0CTF 2018 write up

去年被 0CTF 虐过了之后,有些恋恋不舍(并不),于是今年参加了 0CTF 2018。不算签到题,27 题解出了 3 题(还包括了一道水题),我好菜啊.. 不过有意思的是,Easy User Manage System 这道并不难的题在我首杀之后,比赛第一天内就没有其他队提交了,靠着这题的 1000 分,有幸在榜上逛了几分钟

Easy User Manage System

Welcome to the Easy UMS. Click Here
Since you don’t like the waf, port is changed to 2333.
You need bind your phone with exactly 8.8.8.8 to get flag.

大致流程:注册->登录->(欢迎页面)->验证手机->更改手机

题目中用 IP 来当作手机,通过 80 的 HTTP HEAD 请求来验证(下简称 80 验证码)。挺有意思的题,虽然坑有些多..

首先简单用自己的 IP 注册一个帐户,很快进入了验证码页面。通过收到的验证码进行验证,并进入用户首页。

202.120.7.196 - - [02/Apr/2018:16:29:46 +0800] "HEAD /?684de54c9e9bea8c720dfded05b25b95 HTTP/1.1" 404 0 "-" "-" -
欢迎页面

目标是要我们将手机更改为 8.8.8.8。进入更改手机的页面,需要输入一个 “proof of work” 验证码及新手机。尝试输入 8.8.8.8 提交,发现页面卡了几秒,然后跳转到了验证手机页面。

联想到上面两次发送 80 验证码请求时,速度微妙的变化。可以看出这之中有网络请求阻塞,想到了可能要用条件竞争来解题。

手机未通过验证时,首页会直接跳转到验证页面,猜测数据库 / session 中只有这几个字段:

mobile(当前手机):1.2.3.4
verified(是否已验证):T/F
code(当前验证码):a1b2c3….

为了让手机被设置为 8.8.8.8,同时通过验证,我们尝试同时发起一个更改手机为 8.8.8.8 的请求和一个验证自己的手机(IP)的请求。

可以看到,虽然我们是同时发送的两个请求,但那个服务器不需要发送 80 验证码的“验证手机请求”也跟着发送 80 验证码的“更改手机”请求卡了一会儿。

猜测服务器做了限制,强制让请求按顺序执行,最终结果是,手机改为了 8.8.8.8,但 80 验证码在数据库 / session 中也已经改为了 8.8.8.8 所对应的新码。服务器顺序处理第二个请求时,旧码已失效,无法通过验证。

联想到 0CTF 2017 的 Temmo’s tiny shop 使用了多个 session 进行条件竞争,本题的请求顺序限制(锁)也许也是对 session 所做的。于是开两个浏览器(两个不同的 session),再来一次试试:

两个请求很快完成,flag 也出现了:

tctf{session_database_keep_updated}

LoginMe

Hi, I’ve been learning NodeJS for a day!!!
I’m trying to make a simple login page with this awesome language, can you please check it out.
LoginMe1
LoginMe2
LoginMe3
Source code

看了下代码,页面输出只有“ok”和出错两种可能,很明显是盲注。估计因为大家都在注,主办方甚至提供了 3 个一样的环境 = =##

题目中允许通过提交的请求,进行多次正则替换。用于替换的字串不支持 # ( ) 三个符号,且经过了 JSON.stringify,左右两侧会出现引号。

正则左右会加 #,但这里可以很容易的用 #? ... |1# 的方法使左右的 # 失效,正则基本可以没有限制地随意写。

因此,难点在于,如何将其中的 ... && hex_md5(#password#) == this.password_column){ ... 替换为以下形式中的一种,以便进行逐位爆破:

... && #password# == this.password_column.substr( ... )){ ... // 无法替换出 ( ),放弃
... && #password# <= this.password_column){ ... // <= 左右会出现引号,无法处理,放弃
... && #password# <= this["password_column"]){ ... // <= 左右会出现引号,但可以用作 "password" 和 "password_column" 中的引号,可以尝试
// if(this.username == #username# && #username# == "admin" && hex_md5(#password#) == this.password_column){return 1;}else{return 0;}

// 一、替换用户名
username      -> "admin"

// 结果: if(this.username == "admin" && "admin" == "admin" && hex_md5(#password#) == this.password_column){return 1;}else{return 0;}

// 二、替换密码判断部分(用到了正则中的断言)
(?=\){)      ->   "] +"
== this.      ->   "<=this["
hex.*?rd.*?"  ->   "猜测的密码"
"(?=\))       ->   ""

// 结果: if(this.username == "admin" && "admin" == "admin" && "猜测的密码" <=this["password_column"] +""){return 1;}else{return 0;}

// 三、将 return 0 换为会报错的 return a
0;             -> "asuna"
asuna          -> "+a+"

// 结果: if(this.username == "admin" && "admin" == "admin" && "猜测的密码" <=this["password_column"] +""){return 1;}else{return ""+a+""}

然后,我错误的估计了爆破的工作量,进行了长达半小时的手动爆破(早知道就写代码了 = =!):

flag{13fc892df79a86494792e14dcbef252a}

Coxxs

0CTF 2018 write up》有7个想法

  1. 看了师傅的wp,收获很大,在博文中引用了师傅的内容,感谢。

    1. ;w; 我技术还很菜,很高兴能有所帮助。另外感谢 LoginMe 的 “替换密码判断部分” 一处笔误的指正

  2. 原来你就是那个秒了8.8.8.8那题的老哥,我当时就一直在想谁那么牛逼。给师傅点个赞

  3. 师傅 您好 我想知道 Easy User Manage System 怎么查看 80端口的验证码 问题可能有点弱智 见谅

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注