Webhacking.kr old-35
Challenge
Bypass the phone-number insert with SQL injection.
通过 SQL injection 绕过电话号码写入。
1 | https://webhacking.kr/challenge/web-17/ |
Analysis
?view_source=1 给出源码:
1 |
|
phone 直接拼入 VALUES
的第三个字段,外层没有引号。它的过滤规则禁止
* / = select - # ;,但允许单引号、逗号和括号,足以闭合当前元组并追加第二个元组。
GET 参数 id 最多 5 字节,且不能包含
admin(大小写不敏感),因此直接提交 id=admin
会被拒绝。通过 phone 追加的行不经过这项 id
检查。
Solution
保持 id=guest,将 phone 设为
1),('guest2','<REMOTE_ADDR>',1。源码末尾的
) 补齐第二个元组,拼接后的 SQL 为:
1 | insert into chall35(id,ip,phone) values('guest','<REMOTE_ADDR>',1),('guest2','<REMOTE_ADDR>',1) |
第二行的 ip 必须与服务端看到的 REMOTE_ADDR
一致。可先将当前请求出口的公网 IP 作为候选值;经过代理或 NAT
时,候选值应取实际出口地址。页面末尾的 HTML 注释仅列出 ip
匹配当前 REMOTE_ADDR 的记录,可以用 guest2
行检查候选值。该探测会写入数据库。
以下为解码后的 query 参数,<candidate-ip>
需替换为候选地址;发送请求时对参数值进行 URL encoding:
1 | id=guest |
历史实测响应中的 HTML 注释为:
1 | <!-- |
本次插入的 guest2 出现在注释中,说明多值
INSERT 生效,且候选地址与当前请求的
REMOTE_ADDR 匹配。仅返回 Done 只能说明 SQL
执行成功,无法证明地址匹配;若已有同名记录,应换用新的探测标识,避免把旧行当作本次结果。后续请求需保持相同出口地址。
将第二行的 id 改为
admin,并填入已确认的地址:
1 | id=guest |
根据源码,该行会使
select ip from chall35 where id='admin' and ip='<REMOTE_ADDR>'
命中,随后调用 solve(35),再尝试执行
delete from chall35。删除语句没有 WHERE
条件,作用范围是整张表;源码未检查删除是否成功。这里的
admin 只出现在 phone
中,未命中该参数的过滤规则。