ISC 2018 蓝鲸魔塔线上挑战赛 Write Up


WEB

蓝鲸文件管理系统

首先访问网站,看到几个关键功能:上传、改名、删除。对应的文件为:upload.php,rename.php和delete.php

 在网站源文件中找到相应文件并查看结构,发现有全局配置文件:common.inc.php。在common.inc.php中可以看到全局进行了转义,这样常规注入少了大部分。遍观代码,输入处没有任何反转义、反解压、数字型等特殊情况,基本可以确定不存在直接的注入漏洞。

接下来观察上传处的代码:upload.php,第一段检查

段代码定义了我们如何上传文件以及允许后缀名(检查后缀)并且数据库中存储了上传的文件名和后缀名(分开存储)

第二段存储

可见,上传的文件名走过的流程是:

$file[‘name’] -> pathinfo() –> $path_parts["filename"] -> quote() -> insert

由于经过了pdo的quote方法转义,所以此处也不存在注入。

再看rename.php改名功能

根据$req['filename']从数据库里查询到已存在的一行,并调用update语句进行修改。

但这里oldname=‘{$result[’filename‘]}’ 将从数据库里查出的$result[‘filename’]再一次入库,结果造成一个“二次注入“。

这个二次注入有什么作用呢?这是个update的类型的二次注入,所以我们用来去修改文件名。源码中的上传,重命名,删除功能,我们一次查看首先upload.php是文件上传的操作,但可见上传处对文件进行了白名单验证

导致我们无法上传恶意文件。

其次是delete.php,这个文件其实是个烟雾弹,删除操作并不能利用。再次是rename.php,这里明显是getshell的关键。

这几行最为重要:

Oldname和newname,有几个特点:

  •  后缀相同,都是$result['extension']
  •  oldname的文件名来自数据库,newname的文件名来自用户输入

 

首先后缀相同这个特点,就导致getshell似乎难以完成,如果要getshell那么一定要将非“.php”后缀的文件重命名成“.php”的文件。后缀相同怎么重命名?

除非后缀为空!

所以我们的update型注入就开始派上用场了。通过update型注入,我们可以将数据库中extension字段的值改为空,同时也可以控制filename的值,那么等于说我能控制rename函数的两个参数的值,这样getshell就近在咫尺了。

 

但是在重命名的时候还有个坑:if(file_exists($oldname))

我虽然通过注入修改了filename的值,但我upload目录下上传的文件名是没有改的。因为我利用注入将extension改为空了,那么实际上数据库中的filename总比文件系统中真实的文件名少一个后缀。

再次上传一个新文件,这个文件名就等于数据库里的filename的值就好了。

  • 首先构造注入用的文件和getshell的文件。
  • 插入语句为:insert into `file` ( `filename`, `view`, `extension`) values( ‘文件名’, ‘后缀名’);
  • 更新语句为:update `file` set `filename`=‘新文件名’, `oldname`=‘查询文件名' where `fid`=fid
  • 首先想要后缀名为空就得有一个语句为
  • Extention = ‘’并且还原一个我们可以上传的文件名
  • 构造如下: update `file` set `filename`=‘x.jpg’, `oldname`=‘’,extension=‘’,filename=‘x.jpg’ where `fid`=fid
  • newname就是:’,extension=‘’,filename=‘x.jpg 而原始上传文件名应该为这次的oldname,但是注意加上多一个后缀名因为后缀名将入库到extension中。原始为: ’,extension=‘’,filename=‘x.jpg .jpg

整理为:需要两个文件,一个后缀名为:

',extension='',filename='x.jpg.jpg

一个木马文件,重命名为x.jpg

然后找到rename功能,输入文件名重命名

注意这里提示输入不加后缀名~所以我们输入的就是filename

注入后,数据库中filename=x.jpg,extension=‘’,文件系统中文件名为x.jpg.jpg

 

接着上传真正包含webshell的x.jpg

 

再次利用rename改名,就不会被添加上jpg的后缀名了


 蓝鲸笔记系统

访问网站后注册登录(看似漏洞可能存在的点很多,登录功能是否存在注入可以进行探测,链接存在文件包含的嫌疑,笔记提交存在注入或者xss或者CSRF的嫌疑等等)

有一条笔记,带来了一个提示:

 

告诉我们有一个dbinit.sql于是下载该文件,其中有flag表

这就说明,flag在数据库中,此题目很有可能是sql注入(但是希望没有跑偏)在看一下题目给出的url,开始就很怀疑这样的动态url。

Index.php?action=front&mode=index

Index.php?action=front&mode=newnote

Index.php?action=front&mode=delete

那么index就是入口,直接访问/front/index.php和/front/delete.php等连接,发现访问被拒绝了。

猜测这样的动态连接在脚本中是这样构造的:

Include ($action.’/’.$mode.’.php’)那么包含了php后缀名的文件,不需要再进行截断就可以直接利用包含漏洞,于是利用php为协议中的filter过滤方法加base64编码方法获取源代码。

例如:index.php?action=php://filter/read=convert.base64-encode/resource=./&mode=index这样我们就能够审计源代码了。

扫描发现存在后台地址:admin/index.php和admin/login.php

所以重点来审计一下后台的登录功能(利用连接: index.php?action=php://filter/read=convert.base64-encode/resource=admin/&mode=admin/login )

调用了set_login函数,所以我们来看一下set_login函数(common.php)

这⾥设置了cookies和session,cookie是admin|md5(SECURITY_KEY+’admin’),其中SECURITY_KEY是6位随机数.

再来看一下后台的验证登录过程(admin/index.php):

主要调用了check_login()和get_level()两个函数

两个函数在:common.php中

这两个函数返回用户的userid和level,如果cookie验证失败,则userid为false。level如果是0,也会返回false但是如果我们没有登录,而且绕过了cookie的验证,那么? $_SESSION[‘userid‘]和$_SESSION[’level’] 的默认初始值都是null。

在php中,$null!==false是反回true的,所以我们只要能够伪造cookie,就可以绕过这⾥的验证

进入后台后,主要看下生成cookie至关重要的值$_SESSION[‘SECURITY_KEY’]的产生过程:

当用户一访问,如果没有设置token和security_key就会使用rand生成随机字符串,但是前面我们也说过,如果rand函数不适用srand产生种子是有缺陷的。

不过这里无法绕过,因为前面测试发现php已经修复了漏洞。所以这里的随机字符串我也修改成了两个元素:wh中的6位随机排列,降低了很多难度。

以防今后出现类似题目,还是提供大家一个脚本去进行随机数的预测,这里贴出关键代码:

由于测试发现预测出来的值可

能与原始相差1,还需要计算

可能的范围,其中用了itertools

中的方法去计算笛卡尔积

还有就是需要提前访问两次站点,先生成32位以上的字符串产生预测缺陷。(sek为6位,tooken为16位,而tooken将下发给用户,所以44位情况如下)

 那么如果存在缺陷,多运行几次脚本总能够猜测出来。但是本环境中不存在了,我们就用到暴力破解啦。

这样我们就能够获取到网站管理员的cookie了。

利用cookie manager修改当前cookie,访问index.php?action=admin&mode=index就能登录后台啦

 

登录后发现链接:setpagenum功能

再次利用刚才的LFI漏洞下载源码审计

Page参数虽然没有经过单引号包裹,但是经过了is_number()检查,导致无法注入。

接着看到index.php中:

 

Page_size直接放入sql语句中进行查询,如果可以控制page_size,这里就能够存在注入,但是好像page_size在代码中设置了只能是整形?

好像不能够利用,打开最开始下载的dbinit.sql查看,竟然发现了num是varchar的字符类型,典型的数据库和代码不一致。

 

接下来只需要利用php 5.x中的is_numberic缺陷(php 7.0已修复)认为0x…是整数,就能够二次注入了

先看note的字段数为:4

所以构造注入语句为:1 union select 1,flag,3,4 from flags

然后将语句转换为16进制数据:0x3120756E696F6E2073656C65637420312C666C61672C332C342066726F6D20666C616773提交就可以注入了。