实战 | 记一次攻防演练

渗透技巧 2年前 (2021) admin
1,033 0 0

文章转载于先知社区,作者:jdr

原文链接:https://xz.aliyun.com/t/10470

领导通知,让我打十天攻防,前四天,平平无奇,两个权限,web系统都是外包的,没打进核心内网。

这次攻防,没有给靶标,也没有给资产,全靠自己进行信息搜集。
由于本人不会钓鱼,所以只能打打外网了。
信息搜集主要以厂商系统为主,通过使用fofa,云悉,查ICP备案,APP脱壳逆向,公众号接口,小程序,天眼查查母公司以及子公司的备案和资产信息。通过能直接获取到的资产信息,进行二次信息搜集,主要是以C段,B段和目录扫描为主。

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


第五天主要是以代码审计为主。
通过信息找到找到子公司的一个备案网站系统。
实战 | 记一次攻防演练
根据左上角的功能提示,发现网站存在登录和注册功能,因此尝试注册一个账号。
点击注册按钮,发现跳转到登录界面。很奇怪,貌似注册功能无法正常使用。
实战 | 记一次攻防演练
f12查看源码发现端倪,注册相关的实现代码已经被注释掉了。
因此将注释符号删除,并使用注册功能注册了账号
实战 | 记一次攻防演练此时使用注册功能成功注册了一个账号
实战 | 记一次攻防演练
并登录成功。
实战 | 记一次攻防演练
寻找上传接口,尝试文件上传。但是发现功能点似乎无法正常使用。
实战 | 记一次攻防演练
f12抓取上传接口的数据包。
实战 | 记一次攻防演练
访问该接口,上传表单成功出现。
实战 | 记一次攻防演练
尝试上传正常图片,均不能正常使用。提示都是文件大小不符合。
实战 | 记一次攻防演练

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


尝试尝试寻找后台,也没有找到。
尝试寻找SQL注入,没有找到。
此时发现该网站系统使用了thinkphp框架,但是具体不知道是哪个版本。
常用于获取tp框架版本的方法都是利用报错或者敏感文件,但是这里,似乎都没有。
盲打一波tp5的rce,均失败。
实战 | 记一次攻防演练
此时陷入瓶颈期。
想到了,该系统一定是基于tp框架开发的,但是具体是哪个CMS这里还未知,使用云悉获取该CMS信息也失败了。

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


此时无意间发现,上传接口的title中,泄露了该CMS信息,该cms为pigcms。
实战 | 记一次攻防演练
此时搜索有关该CMS的历史漏洞,通过cnvd平台。
实战 | 记一次攻防演练
尝试利用SQL注入漏洞,复现后均失败。
实战 | 记一次攻防演练

3.4 寻找CMS源码


注:此处找到的源码版本不一定会和目标站点一致。
尝试去寻找源码。官网看了一眼,真的贵。离谱。怎么可能花钱。
实战 | 记一次攻防演练
通过网盘搜索,百度搜索,谷歌搜索的方式,下载了源码。
实战 | 记一次攻防演练本地环境搭建。
看着还挺像那么回事的。
实战 | 记一次攻防演练
确定后台路径。
本机后台
实战 | 记一次攻防演练
目标站点后台。
实战 | 记一次攻防演练
这后台长的不怎么像,影响不大。
试了一下初始密码,没进去。
看了一下后台,注入漏洞挺多的,有tp3.1的注入,也有pigcms的注入,也能文件上传GETSHELL,也能模板注入GETSHELL。
实战 | 记一次攻防演练

3.5 白加黑代码审计


3.5.1 前台任意文件上传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=thumb,content-type的值为switch选择结构中的image/jpg时,指定上传后,文件的后缀名ext是jpg。文件名的命名是随机的,根据时间指定。

读懂逻辑后发现,此处的action_picUpload是无法上传文件获取权限的。
继续审计第二次上传的函数。
action_flashUpload
实战 | 记一次攻防演练
阅读第二个上传函数的逻辑发现,当name的值是filepath,并且content-type的值是flash格式时,能够上传成功,上传后的后缀名是由filename的文件名后缀来确定的。
构造文件上传的poc数据包
实战 | 记一次攻防演练
发现上传成功,回显php文件路径。
查看本地监听的文件路径生成情况,并确定php文件的最后路径。
实战 | 记一次攻防演练
访问后,phpinfo被成功执行。
实战 | 记一次攻防演练

尝试上传到目标站点,并上传成功。
实战 | 记一次攻防演练
此时跟队友分享喜悦,并准备周一打内网。
由于和裁判沟通后,裁判要求,漏洞尽量要周一交。(意思是周末不攻防)
并得知提交0day漏洞是有额外加分。
实战 | 记一次攻防演练

3.5.2 蓝队周末居然上班

等到周六后,下午访问一下phpinfo看看。结果发现,蓝队居然上班了。phpinfo的页面内容变成了hack.
实战 | 记一次攻防演练
实战 | 记一次攻防演练
页面不是phpinfo?重新上传一下,好家伙,不讲武德,裁判都说休战了,你居然给我搞事情。
实战 | 记一次攻防演练
这是之前已经成功执行的截图。
实战 | 记一次攻防演练

离谱的一批。
继续审计

3.5.3 数据导出+可能的任意文件写入漏洞。


周日,开始重新审计。现在审计的思路主要是想办法拿到数据,并进入后台改配置,这样只要网站不关闭,我就有的是办法做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."';nn"); }
$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]`;n"; } $createtable = $this->db->query("SHOW CREATE TABLE `$tables[$i]` "); $create = $this->db->fetch_next(); $tabledump .= $create['Create Table'].";nn"; $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 .= ");n"; } $this->db->free_result($rows); $startfrom += $offset;
} $tabledump .= "n"; $startrow = $startfrom; $startfrom = 0; } if(trim($tabledump)) { $tabledump = "# time:".date('Y-m-d H:i:s')."n# bupu auto system:http://www.bupu.netn# --------------------------------------------------------nnn".$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这个入口文件给删了。牛逼牛逼。
跟队友说了一下情况。
实战 | 记一次攻防演练

3.5.4 蓝队不讲武德,直接关站

晚上准备写博客。
准备打开目标站点截几个图。
结果发现,蓝队直接给你关站了。笑死。
实战 | 记一次攻防演练
一片红,笑死我了,和队友吐槽大无语事件。
实战 | 记一次攻防演练
等周一明天举报了。
缺乏攻防经验,没有在第一天拿到权限后,做权限维持。
跟裁判反馈后,漏洞最后通过给了一百分,麻了。
不过通过这次攻防增长了不少见识大概懂了一些恶心人的做法。
以后拿到权限,一定做好权限维持,不要相信裁判的鬼话,也不要信红蓝队会守规矩。
实战 | 记一次攻防演练 

实战 | 记一次攻防演练


推荐阅读


实战 | 记一次诡异的网站篡改应急响应


干货 | Certutil在渗透中的利用和详解


实战 | 进程启动技术的思路和研究


干货 | DLL注入常用的几种方式


干货 | 绕过AMSI实现免杀的研究和思路


点赞    在看    评论


实战 | 记一次攻防演练

原文始发于微信公众号(HACK学习呀):实战 | 记一次攻防演练

版权声明:admin 发表于 2021年11月14日 上午1:00。
转载请注明:实战 | 记一次攻防演练 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...