• 记一次攻防演练复盘之计中计


    0x00 前言

    这两天听DR复盘,补充了很多小技能点,真的要非常细心呀,然后下面就是正文了。

    领导通知,让我打十天攻防,前四天,平平无奇,两个权限,web系统都是外包的,没打进核心内网。
    这次攻防,没有给靶标,也没有给资产,全靠自己进行信息搜集。

    0x01 前期信息搜集

    信息搜集主要以厂商系统为主,通过使用fofa,云悉,查ICP备案,APP脱壳逆向,公众号接口,小程序,天眼查查母公司以及子公司的备案和资产信息。通过能直接获取到的资产信息,进行二次信息搜集,主要是以C段,B段和目录扫描为主。

    0x02 黑盒获取权限

    测试网站的功能点,想办法黑盒获取权限。

    0x03 代码审计为主(白加黑治感冒)

    通过信息找到找到子公司的一个备案网站系统。

    根据左上角的功能提示,发现网站存在登录和注册功能,因此尝试注册一个账号。
    点击注册按钮,发现跳转到登录界面。很奇怪,貌似注册功能无法正常使用。

    f12查看源码发现端倪,注册相关的实现代码已经被注释掉了。

    因此将注释符号删除,并使用注册功能注册了账号

    此时使用注册功能成功注册了一个账号

    并登录成功。

    寻找上传接口,尝试文件上传。但是发现功能点似乎无法正常使用。

    f12抓取上传接口的数据包。

    访问该接口,上传表单成功出现。

    尝试上传正常图片,均不能正常使用。提示都是文件大小不符合。

    尝试利用TP框架漏洞获取权限

    尝试尝试寻找后台,也没有找到。

    尝试寻找SQL注入,没有找到。

    此时发现该网站系统使用了thinkphp框架,但是具体不知道是哪个版本。

    常用于获取tp框架版本的方法都是利用报错或者敏感文件,但是这里,似乎都没有。

    盲打一波tp5的rce,均失败。

    此时陷入瓶颈期。

    想到了,该系统一定是基于tp框架开发的,但是具体是哪个CMS这里还未知,使用云悉获取该CMS信息也失败了。

    尝试利用cms的利用漏洞获取网站权限和数据

    此时无意间发现,上传接口的title中,泄露了该CMS信息,该cms为pigcms。

    此时搜索有关该CMS的历史漏洞,通过cnvd平台。

    尝试利用SQL注入漏洞,复现后均失败。

    寻找CMS源码

    注:此处找到的源码版本不一定会和目标站点一致。

    尝试去寻找源码。官网看了一眼,真的贵。离谱。怎么可能花钱。

    通过网盘搜索,百度搜索,谷歌搜索的方式,下载了源码。

    本地环境搭建。
    看着还挺像那么回事的。

    确定后台路径。

    本机后台

    目标站点后台。

    这后台长的不怎么像,影响不大。

    试了一下初始密码,没进去。

    看了一下后台,注入漏洞挺多的,有tp3.1的注入,也有pigcms的注入,也能文件上传GETSHELL,也能模板注入GETSHELL。

    0x04 白加黑代码审计

    前台任意文件上传GETSHELL
    试了一下常规的未授权测试方法,均失败,因此只能考虑审计出前台漏洞了。这里用自己之前写的一个工具,遍历当前目录下指定后缀的文件路径。

    将路径文件字典导入burpsuiteintruter模块的payload中,并去掉payload encoding前面的勾。

    开始爆破。并根据response判断哪些文件是未授权访问的。

    此时成功找到了未授权的入口文件。

    并发现了两处关于文件上传的函数。

    action_picUpload

    public function action_picUpload(){
    	$error=0;
    	if (isset($_FILES['thumb'])){
    		$photo=$_FILES['thumb'];
    		if(substr($photo['type'], 0, 5) == 'image') {
    			switch ($photo['type']) {
    				case 'image/jpeg':
    				case 'image/jpg':
    				case 'image/pjpeg':
    					$ext = '.jpg';
    					break;
    				case 'image/gif':
    					$ext = '.gif';
    					break;
    				case 'image/png':
    				case 'image/x-png':
    					$ext = '.png';
    					break;
    				default:
    					$error=-1;
    					break;
    			}
    			if($error==0){
    				$time=SYS_TIME;
    				$year=date('Y',$time);
    				$month=date('m',$time);
    				$day=date('d',$time);
    				$pathInfo=upFileFolders($time);
    				$dstFolder=$pathInfo['path'];
    				$dstFile=ABS_PATH.'upload'.DIRECTORY_SEPARATOR.'temp'.$ext;
    				//the size of file uploaded must under 1M
    				if($photo['size']>2000000){
    					$error=-2;
    					return $error;
    				}
    			}else {
    				return $error;
    			}
    			//if no error
    			if($error==0){
    				$rand=randStr(4);
    				
    				//delete primary files
    				
    				if(file_exists($dstFolder.$time.$rand.$ext)){
    					unlink($dstFolder.$time.$rand.$ext);
    				}
    				if ($ext!='.gif'&&$ext!='.png'){
    					//save the temporary file 
    					move_uploaded_file($photo['tmp_name'],$dstFile);
    					$imgInfo=getimagesize($dstFile);
    					//generate new files
    					$imageWidth=intval($_POST['width'])!=0?intval($_POST['width']):$imgInfo[0];
    					$imageHeight=intval($_POST['height'])!=0?intval($_POST['height']):$imgInfo[1];
    					bpBase::loadSysClass('image');
    					image::zfResize($dstFile,$dstFolder.$time.$rand.'.jpg',$imageWidth,$imageHeight,1|4,2);
    					$ext='.jpg';
    					//
    				}else {
    					move_uploaded_file($photo['tmp_name'],$dstFolder.$time.$rand.$ext);
    				}
    				if (isset($_POST['channelid'])){//内容缩略图
    					$channelObj=bpBase::loadAppClass('channelObj','channel');
    					$thisChannel=$channelObj->getChannelByID($_POST['channelid']);
    					$articleObj=bpBase::loadAppClass('articleObj','article');
    					$articleObj->setOtherThumb($thisChannel,$dstFile,$dstFolder,$time.$rand,'jpg');
    				}
    				if ($ext!='.gif'&&$ext!='.png'){
    					@unlink($dstFile);
    				}
    				$location='http://'.$_SERVER['HTTP_HOST'].CMS_DIR_PATH.'/upload/images/'.$year.'/'.$month.'/'.$day.'/'.$time.$rand.$ext;
    				$error=0;
    			}
    		}else {
    			$error=-1;
    		}
    	}else {
    		$error=-1;
    	}
    	
    	if ($error==0){
    		
    		echo $location;
    	}else {
    		$errors=array(-1=>'你上传的不是图片',-2=>'文件不能超过2M',-3=>'图片地址不正确');
    		echo $errors[intval($error)];
    	}
    }

    action_picUpload的逻辑是,上传的图片文件时,name=thumbcontent-type的值为switch选择结构中的image/jpg时,指定上传后,文件的后缀名extjpg。文件名的命名是随机的,根据时间指定。

    读懂逻辑后发现,此处的action_picUpload是无法上传文件获取权限的。

    继续审计第二次上传的函数。

    action_flashUpload

    阅读第二个上传函数的逻辑发现,当name的值是filepath,并且content-type的值是flash格式时,能够上传成功,上传后的后缀名是由filename的文件名后缀来确定的。

    构造文件上传的poc数据包

    发现上传成功,回显php文件路径。

    查看本地监听的文件路径生成情况,并确定php文件的最后路径。

    访问后,phpinfo被成功执行。

    尝试上传到目标站点,并上传成功。

    0x05 成功审计0day

    此时跟队友分享喜悦,并准备周一打内网。

    由于和裁判沟通后,裁判要求,漏洞尽量要周一交。(意思是周末不攻防)

    并得知提交0day漏洞是有额外加分。

    0x06 蓝队周末居然上班

    等到周六后,下午访问一下phpinfo看看。结果发现,蓝队居然上班了。phpinfo的页面内容变成了hack.

    页面不是phpinfo?重新上传一下,好家伙,不讲武德,裁判都说休战了,你居然给我搞事情。

    这是之前已经成功执行的截图。

    离谱的一批。
    继续审计


    0x07 某CMS-0day数据导出+可能的任意文件写入漏洞-二次0day

    周日,开始重新审计。现在审计的思路主要是想办法拿到数据,并进入后台改配置,这样只要网站不关闭,我就有的是办法做webshell层面的权限维持,后面再做系统层面的权限维持。

    private function export_database($tables,$sqlcompat,$sqlcharset,$sizelimit,$action,$fileid,$random,$tableid,$startfrom) {
    		$dumpcharset = $sqlcharset ? $sqlcharset : str_replace('-', '', DB_CHARSET);
    
    		$fileid = ($fileid != '') ? $fileid : 1;		
    		if($fileid==1 && $tables) {
    			if(!isset($tables) || !is_array($tables)) showMessage('请选择要备份的表');
    			$random = mt_rand(1000, 9999);
    			setCache('backupTables',serialize($tables));
    		} else {
    			if(!$tables = unserialize(getCache('backupTables'))) showMessage('请选择要备份的表');
    		}
    		if($sqlcharset) {
    			$this->db->query("SET NAMES '".$sqlcharset."';
    
    ");
    		}
    		
    		$tabledump = '';
    
    		$tableid = ($tableid!= '') ? $tableid - 1 : 0;
    		$startfrom = ($startfrom != '') ? intval($startfrom) : 0;
    		for($i = $tableid; $i < count($tables) && strlen($tabledump) < $sizelimit * 1000; $i++) {
    			global $startrow;
    			$offset = 100;
    			if(!$startfrom) {
    				if($tables[$i]!=AUTO_TABLE_PREFIX.'session') {
    					$tabledump .= "DROP TABLE IF EXISTS `$tables[$i]`;
    ";
    				}
    				$createtable = $this->db->query("SHOW CREATE TABLE `$tables[$i]` ");
    				$create = $this->db->fetch_next();
    				$tabledump .= $create['Create Table'].";
    
    ";
    				$this->db->free_result($createtable);
    							
    				if($sqlcompat == 'MYSQL41' && $this->db->version() < '4.1') {
    					$tabledump = preg_replace("/TYPE=([a-zA-Z0-9]+)/", "ENGINE=\1 DEFAULT CHARSET=".$dumpcharset, $tabledump);
    				}
    				if($this->db->version() > '4.1' && $sqlcharset) {
    					$tabledump = preg_replace("/(DEFAULT)*s*CHARSET=[a-zA-Z0-9]+/", "DEFAULT CHARSET=".$sqlcharset, $tabledump);
    				}
    				if($tables[$i]==AUTO_TABLE_PREFIX.'session') {
    					$tabledump = str_replace("CREATE TABLE `".DB_PRE."session`", "CREATE TABLE IF NOT EXISTS `".DB_PRE."session`", $tabledump);
    				}
    			}
    			$numrows = $offset;
    			while(strlen($tabledump) < $sizelimit * 1000 && $numrows == $offset) {
    				if($tables[$i]==AUTO_TABLE_PREFIX.'session') break;
    				$sql = "SELECT * FROM `$tables[$i]` LIMIT $startfrom, $offset";
    				$numfields = $this->db->num_fields($sql);
    				$numrows = $this->db->num_rows($sql);
    				$fields_name = $this->db->get_fields($tables[$i]);
    				$rows = $this->db->query($sql);
    				$name = array_keys($fields_name);
    				$r = array();
    				while ($row = $this->db->fetch_next()) {
    					$r[] = $row;
    					$comma = "";
    					$tabledump .= "INSERT INTO `$tables[$i]` VALUES(";
    					for($j = 0; $j < $numfields; $j++) {
    						$tabledump .= $comma."'".mysql_real_escape_string($row[$name[$j]])."'";
    						$comma = ",";
    					}
    					$tabledump .= ");
    ";
    				}
    				$this->db->free_result($rows);
    				$startfrom += $offset;
    				
    			}
    			$tabledump .= "
    ";
    			$startrow = $startfrom;
    			$startfrom = 0;
    		}
    		if(trim($tabledump)) {
    			$tabledump = "# time:".date('Y-m-d H:i:s')."
    # bupu auto system:http://www.bupu.net
    # --------------------------------------------------------
    
    
    ".$tabledump;
    			$tableid = $i;
    			$filename = date('Ymd').'_'.$random.'_'.$fileid.'.sql';
    			$altid = $fileid;
    			$fileid++;
    			
    			$backUpFolder=ABS_PATH.DIRECTORY_SEPARATOR.'backup';
    			if (!file_exists($backUpFolder)&&!is_dir($backUpFolder)){
    				mkdir($backUpFolder,0777);
    			}
    			$bakfile_path = ABS_PATH.'backup'.DIRECTORY_SEPARATOR.'data'.date('Y-m-d',SYS_TIME);
    			if (!file_exists($bakfile_path)&&!is_dir($bakfile_path)){
    				mkdir($bakfile_path,0777);
    			}
    			$bakfile = $bakfile_path.DIRECTORY_SEPARATOR.$filename;
    			if(!is_writable($bakfile_path)) showMessage('backup文件夹不可写');
    			file_put_contents($bakfile, $tabledump);
    			@chmod($bakfile, 0777);
    			showmessage('正在备份,请不要关闭浏览器'." $filename ", '?m=manage&c=database&a=action_export&sizelimit='.$sizelimit.'&sqlcompat='.$sqlcompat.'&sqlcharset='.$sqlcharset.'&tableid='.$tableid.'&fileid='.$fileid.'&startfrom='.$startrow.'&random='.$random.'&allow='.$allow);
    		} else {
    		  $bakfile_path = ABS_PATH.'backup'.DIRECTORY_SEPARATOR.'database';
    		   //file_put_contents($bakfile_path.DIRECTORY_SEPARATOR.'index.html','');
    		   delCache('backupTables');
    		   showmessage('备份成功,数据备份在了“/backup/data'.date('Y-m-d',SYS_TIME).'”文件夹中');
    		}
    	}

    通过阅读此处的代码逻辑,发现指定数据表名称,即可导出数据。

    找到该sql文件路径。

    前端访问并下载成功。

    可以通过此方法,拿到后台管理员账号密码。

    继续审计发现,此处的导出时,文件名可控,内容可控。

    此时可以发现,可能可以截断后缀。

    尝试截断,并成功。

    尴尬的是,文件内容并没有写入。

    查了相关资料后发现。

    用冒号截断的确会这样,但是用windows文件流截断,文件也并没有生成。这就很麻烦了。

    此处也没有想到比较好的方法去绕过。

    就暂时放着了。

    下午的时候尝试去下一下数据表的文件,结果发了几个数据包。

    蓝队直接将admin.php这个入口文件给删了。牛逼牛逼。

    跟队友说了一下情况。

    蓝队不讲无德,直接关站

    晚上准备写博客。
    准备打开目标站点截几个图。
    结果发现,蓝队直接给你关站了。笑死。

    一片红,笑死我了,和队友吐槽大无语事件。

    0x08 总结

    1、蓝队不讲武德。

    2、掉以轻心听信裁判话。

    3、该搞搞,不应该拖延。

    转载请注明:Adminxe's Blog » 记一次攻防演练复盘之计中计

  • 相关阅读:
    Java中==和equals的区别
    (转)JAVA-反射机制的使用
    JAVA三框架工作原理是什么?
    Spring的IoC模式
    JavaEE中为什么出现中文乱码?
    Android--Apache HttpClient(2种实现)
    Android之网络----使用HttpClient发送HTTP请求(通过get方法获取数据)
    Android—Http连接之GET/POST请求
    2014 12 04
    struts2的HelloWorld的基本过程
  • 原文地址:https://www.cnblogs.com/cn-gov/p/15496624.html
Copyright © 2020-2023  润新知