记一次攻防演练

简介:

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

这次攻防,没有给靶标,也没有给资产,全靠自己进行信息搜集。

由于本人不会钓鱼,所以只能打打外网了。

前期信息搜集

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

第五六七天

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

第五天主要是以代码审计为主。

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

效果

根据左上角的功能提示,发现网站存在登录和注册功能,因此尝试注册一个账号。

点击注册按钮,发现跳转到登录界面。很奇怪,貌似注册功能无法正常使用。

效果

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。

效果

白加黑代码审计

前台任意文件上传GETSHELL

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

效果

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

效果

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

效果

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

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

action_picUpload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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被成功执行。

效果

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

效果

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

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

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

效果

蓝队周末居然上班

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

效果

效果

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

效果

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

效果

离谱的一批。

继续审计

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

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

效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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."';\n\n");
}

$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'].";\n\n";
$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.net\n# --------------------------------------------------------\n\n\n".$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这个入口文件给删了。牛逼牛逼。

跟队友说了一下情况。

效果

蓝队不讲武德,直接关站

晚上准备写博客。

准备打开目标站点截几个图。

结果发现,蓝队直接给你关站了。笑死。

效果

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

效果

等周一明天举报了。

总结

缺乏攻防经验,没有在第一天拿到权限后,做权限维持。

后续

跟裁判反馈后,漏洞最后通过给了一百分,麻了。
不过通过这次攻防增长了不少见识大概懂了一些恶心人的做法。

以后拿到权限,一定做好权限维持,不要相信裁判的鬼话,也不要信红蓝队会守规矩。

效果

Author: jdr
Link: https://jdr2021.github.io/2021/10/31/记一次攻防演练/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.