HCTF2018 Warm up writeup
拿到题目之后发现这是一张滑稽的脸。查看源代码之后发现有注释的source.php。遂访问。得到源代码。
接下来就是复杂的代码审计工作。发现源代码当中存在hint.php。遂访问。得到“flag not here, and flag in ffffllllaaaagggg”。
源代码当中首先定义了一个类。类当中只是定义了一个方法,是一个checkfile的函数。观察if的代码块,得知我们可以用任意方法传入参数file,参数file的值可控。需要满足的条件是file参数的值不为空,参数的值类型为字符串且其能够通过上述类中定义的checkfile方法。接下来才可以include这个参数。
本来笔者认为这道题是考察绕过各种过滤去进行php文件包含读源码。但是我好像想多了,因为根据提示,hint.php已经告诉了我们flag就在ffffllllaaaagggg文件当中,那么我们只需要包含这个文件就可以了。不为空以及参数的值为字符串类型是非常好满足的。那么我们就要把重心放在如何能够绕过checkfile的检查机制,令其返回值为true并包含我们想要的ffffllllaaaagggg文件即可得到flag。
于是开始艰难的代码审计过程。首先熟悉一下PHP当中的类。PHP当中的类是为了方便代码的编写者在使用一类方法去完成一系列任务所创造的一种模式。事实上这道题直接定义一个新函数是一样的。
笔者写这篇博客实际上是面向初学者(也就是我自己)的,以便自己日后查看。说一下这个类中的checkfile函数当中所用到的知识点。
1.“||”表示或,也就是说,该类当中的
p
a
g
e
变
量
必
须
满
足
其
不
为
空
或
是
其
必
须
为
字
符
串
类
型
即
可
执
行
下
一
步
,
否
则
输
出
“
y
o
u
c
a
n
‘
t
s
e
e
i
t
.
”
,
并
返
回
f
a
l
s
e
。
函
数
执
行
失
败
。
2.
上
述
的
条
件
是
非
常
好
达
成
的
。
达
成
之
后
执
行
下
一
个
代
码
块
。
i
n
a
r
r
a
y
(
a
,
b
)
是
在
b
数
组
当
中
匹
配
a
的
值
,
若
匹
配
到
,
则
返
回
t
r
u
e
,
否
则
返
回
f
a
l
s
e
。
观
察
一
下
page变量必须满足其不为空或是其必须为字符串类型即可执行下一步,否则输出“you can`t see it.”,并返回false。函数执行失败。 2.上述的条件是非常好达成的。达成之后执行下一个代码块。in_array(a,b)是在b数组当中匹配a的值,若匹配到,则返回true,否则返回false。观察一下
page变量必须满足其不为空或是其必须为字符串类型即可执行下一步,否则输出“youcan‘tseeit.”,并返回false。函数执行失败。2.上述的条件是非常好达成的。达成之后执行下一个代码块。inarray(a,b)是在b数组当中匹配a的值,若匹配到,则返回true,否则返回false。观察一下whitelist当中的值,分别为source.php以及hint.php。若想令其为true,则需要令我们的
p
a
g
e
的
值
为
s
o
u
r
c
e
.
p
h
p
或
h
i
n
t
.
p
h
p
的
其
中
一
个
。
则
直
接
可
以
直
接
令
c
h
e
c
k
f
i
l
e
函
数
返
回
的
结
果
为
t
r
u
e
。
所
以
在
这
个
地
方
我
们
可
以
随
便
传
s
o
u
r
c
e
.
p
h
p
或
者
h
i
n
t
.
p
h
p
。
就
可
以
实
现
文
件
包
含
。
3.
即
便
是
没
有
验
证
为
t
r
u
e
,
我
们
也
可
以
继
续
往
下
执
行
。
反
正
只
要
最
后
的
结
果
是
t
r
u
e
即
可
了
。
下
一
个
代
码
块
定
义
了
一
个
新
的
变
量
page的值为source.php或hint.php的其中一个。则直接可以直接令checkfile函数返回的结果为true。所以在这个地方我们可以随便传source.php或者hint.php。就可以实现文件包含。 3.即便是没有验证为true,我们也可以继续往下执行。反正只要最后的结果是true即可了。下一个代码块定义了一个新的变量
page的值为source.php或hint.php的其中一个。则直接可以直接令checkfile函数返回的结果为true。所以在这个地方我们可以随便传source.php或者hint.php。就可以实现文件包含。3.即便是没有验证为true,我们也可以继续往下执行。反正只要最后的结果是true即可了。下一个代码块定义了一个新的变量_page,mb_substr(
a
,
a
,
b
)
指
的
是
截
取
变
量
a
当
中
从
a
开
始
到
b
结
束
的
部
分
,
包
括
a
b
本
身
,
需
要
注
意
的
是
这
是
从
0
开
始
的
,
也
就
是
第
一
个
字
符
的
数
字
是
0
。
m
b
s
t
r
p
o
s
(
a,a,b)指的是截取变量a当中从a开始到b结束的部分,包括ab本身,需要注意的是这是从0开始的,也就是第一个字符的数字是0。mb_strpos(
a,a,b)指的是截取变量a当中从a开始到b结束的部分,包括ab本身,需要注意的是这是从0开始的,也就是第一个字符的数字是0。mbstrpos(a,"?")指的是在变量a当中匹配问号第一次出现的位置,若匹配到,则返回其值,若匹配不到,则返回false。
a
.
′
?
′
表
示
的
是
在
变
量
a
的
值
的
后
方
追
加
一
个
问
号
进
去
。
换
句
话
来
说
,
这
个
a.'?'表示的是在变量a的值的后方追加一个问号进去。换句话来说,这个
a.′?′表示的是在变量a的值的后方追加一个问号进去。换句话来说,这个_page变量的值为
p
a
g
e
变
量
问
号
第
一
次
出
现
的
前
一
部
分
(
不
包
括
问
号
)
。
4.
下
方
继
续
进
行
了
一
个
与
上
方
相
同
的
判
断
,
看
page变量问号第一次出现的前一部分(不包括问号)。 4.下方继续进行了一个与上方相同的判断,看
page变量问号第一次出现的前一部分(不包括问号)。4.下方继续进行了一个与上方相同的判断,看_page变量的值是否能够匹配到白名单中的内容,若能匹配到,则反馈true,不执行接下来的代码。
5.下方则是将我们传入的参数的值进行了urldecode一次,并将其值赋值给
p
a
g
e
变
量
。
再
对
_page变量。再对
page变量。再对_page变量的值进行同样的in_array处理,截取问号前的数值。若其能够匹配到白名单的当中的两个文件名,则返回true。
正解个人认为分为两个部分。第一部分是自己想出来的:
1.我们的根本目的是为了读取出来ffffllllaaaagggg文件,我们不知道这个文件在哪里,不在当前目录,因为直接访问是访问不到的,所以猜想其各个字母都出现了四次,是不是应该向上跳四次。实际上我们满足传入的file参数的值不为空且其为字符串是非常简单的,随便传什么参数都可以。所以只需要考虑绕过checkfile函数就可以。绕过这个函数的判断实际上这个函数当中可以返回为true的地方有三处,只要令任何一个地方的返回值为true就可以不执行下一部分的代码。那么我们就只需要满足其能够读取出我们想要的flag文件即可。若想令第一部分为true只能令$page的值为source.php或者是hint.php,遂不考虑。直接进入下一阶段。
下一阶段想满足true的条件是参数的值若在其出现第一次问号之前的部分为source.php或者hint.php即可。题中已经在我们需要判断的值最后方追加了一个问号,那么我们要向令其能够返回两个白名单当中的值只需要令传入的参数中的值为白名单+问号即可。因为index.php当中的源代码就在source.php当中,所以令传入的参数的值为source.php?file=ffffllllaaaagggg即可实现文件包含以达到目的。传入这个参数之后发现flag并没有显现,那么我们跳出这个目录数次即可。也就是用…/来跳出就行。再跳了五次之后,flag就出现了。最后的payload就是:?file=source.php?file=…/…/…/…/…/ffffllllaaaagggg
2.这个是传统解法,据说是参照了Phpmyadmin4.8.1文件包含漏洞当中的内容进行的。source.php%253f/…/…/…/…/…/ffffllllaaaagggg%253f为?的双重urlencode,使用双重编码可以使其前方的source.php被视为文件夹。故其因为最后一个if判断前会进行一个urldecode,故,我们要将当前的payload中的问号进行二次URLencode。