简介: 关于本人开发的项目VulnScan的安装教程
一、前置安装
1. 安装 Acunetix,获取 APIKey
浏览器登录,进入主页
点击Profile
,在页面最下方获取APIKey
2. 将 acunetix 证书添加到 java 信任库
步骤 1:导出服务器证书
可以使用浏览器访问该 URL,然后导出证书
步骤 2:找到 Java 信任库路径
- Java 默认信任库位于
JAVA_HOME/lib/security/cacerts
。 - 可通过
echo $JAVA_HOME
或检查环境变量确认路径。
步骤 3:将证书导入信任库
执行 keytool 导入命令:
1
keytool -import -alias MyServerCert -keystore $JAVA_HOME/lib/security/cacerts -file server.crt
默认密码:changeit
出现提示时输入 yes
确认信任。
步骤 4:验证导入结果
1
keytool -list -alias MyServerCert -keystore $JAVA_HOME/lib/security/cacerts
二、项目安装
- 手动创建
.env
文件,示例
1
2
3
4
5
DB_USERNAME=vulnscan
DB_PASSWD=xxxx
DB_URL=jdbc:mysql://localhost:3306/vulnscan?useUnicode=true&characterEncoding=utf8&&serverTimezone=GMT%2b8
API_URL=https://desktop-jv0cb08:3443/api/v1/
API_KEY=1986ad8c0a5b3df4xxxxxxcxxxx8c66881d4
- 手动创建数据库,手动运行
vulnscan.sql
文件创建数据库表 - 注册用户后在数据库中手动修改用户
role
为ADMIN
三、技术架构
开发环境:Windows10 操作系统,JAVA 语言,JDK 版本 15,数据库 MySQL,IDE 工具为 IDEA。
系统框架 Spring Boot、Spring Security,持久层框架 MyBatis-Plus,前端框架 Bootstrap,通信技术 WebSocket,验证码工具 Kaptcha。
四、功能介绍
AWVS API 相关功能:通过 RestTemplate 向 AWVS 发送和获取数据,包括目标、扫描、漏洞、报告等相关 API。
登录注册功能:通过 Kaptcha、spring security 实现验证码登录,密码加密,用户验证。
目标扫描功能:添加扫描目标地址,并设置扫描速度、扫描类型、登录设置(用户名密码登录、Cookie 登录)等信息。添加扫描后利用 WebSocket 实时监测扫描状态,并将漏洞分布和扫描进度信息显示在前端页面。
查看漏洞功能:列表显示扫描出的全部漏洞。可查看单个扫描目标的漏洞或单个扫描目标的单个漏洞严重程度的漏洞信息。点击漏洞名称可查看漏洞详情。
扫描报告和漏洞报告导出功能:将扫描结果或漏洞信息导出为 html 或 pdf 文件。可选择导出的模板,如 CWE 2011、OWASP Top 10 2017、ISO 27001 等。
WebSocket 功能:1.调用异步线程获取扫描状态信息。2.调用异步线程生成报告链接等信息,结束后返回状态信息。3.回复前端的心跳检测信息。
数据校验功能:对前端的数据进行 JSR303 数据校验,自定义枚举校验。
信息统计功能:对用户的扫描记录和扫描出的漏洞信息进行统计,使用 Echarts 插件显示图表信息。
1. 数据库设计
根据上述需求,我们可以设计如下数据库:
数据库名:vulnscan,字符集:utf8mb4,排序规则:utf8mb4_unicode_ci
- 用户表(users):记录用户信息,包括用户名、密码、邮箱、角色等字段。
- 扫描记录表(scan_record):记录扫描记录信息,包括扫描类型、扫描网站地址、扫描时间、用户名等字段。
- 漏洞信息表(vuln_info):记录漏洞基本信息。
- 漏洞报告表(scan_roport):记录生成的扫描报告,漏洞报告。
2. 主动扫描
点击添加扫描按钮,弹出表单,填写 url,描述,扫描速度,扫描类型等,点击确定。后端先发送 url 和描述获取 targetId。
1
2
3
4
5
6
7
8
9
10
11
{
"address": "127.0.0.1:8080",
"criticality": 10,
"description": "",
"type": "default",
"domain": "127.0.0.1",
"target_id": "644e089f-57b6-437e-bad2-c0fe8483dde2",
"target_type": null,
"canonical_address": "localhost",
"canonical_address_hash": "77d7e6fbb0a249a2f2c3d5da849834dd"
}
将结果保存到数据库,之后添加扫描任务,开始扫描。
前端显示:id,扫描状态(completed,failed,aborted,scheduled,processing),扫描目标,扫描类型,漏洞、添加时间、到处报告,删除任务。
3. 表单校验、分组校验:JSR303
1
2
3
4
5
<!-- javax.validation -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
1
2
3
4
5
6
7
8
package javax.validation.constraints 包下注解
Entity成员添加注解,可定制报错信息
@Email(message="email格式不正确",groups={addGroup.class,updateGroup.class})
不为空
@NotBlank 至少包含一个空格
@NotNull 验证注解的元素值不是null
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@Pattern(regexp="") 正则表达式,自定义校验
1
2
3
4
5
6
7
8
9
//Controller形参添加@Valid开启校验。紧跟校验参数后加 BindingResult result 获取校验结果
//使用@Validated({})进行分组校验
public R Login(@Valid @RequestBody UsersEntity user, BindingResult result){
if(result.hasErrors()){
//数据校验不通过
}else {
//数据校验通过
}
}
4. 统一异常处理
@ControllerAdvice
1
2
3
4
//新建ControllerAdviceException.java,指定异常处理范围
@ControllerAdvice(basePackages = "com.atlxc.VulnScan.product.controller")
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException exception){}
5. 多线程设计
添加扫描后调用的更新扫描状态方法
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
@Async("connectorExecutor")
public void getScanRecordStatus(String targetId) {
//手动注入bean,获取TargetService、ScanRecordService、ScanService、VulnService、VulnInfoService
......
//循环获取scanid
......
while (true) {
Thread.sleep(INTERVAL);
//通过scanid获取扫描状态
ScanRecordEntity tmpEntity = scanService.getStatus(scanId);
// 比较SeverityCounts变化
......
//severityCount 变化时,更新severityCount和status
//更新status severity
......
scanRecordService.updateById(entity);
//条件筛选获取漏洞信息
......
JSONObject jsonObject = vulnService.selectVulns(params);
JSONArray vulnInfoArray = jsonObject.getJSONArray("vulnerabilities");
for (int i = 0; i < vulnInfoArray.size(); i++) {
JSONObject item = vulnInfoArray.getJSONObject(i);
//避免重复保存
......
//获取数据并保存到数据库
......
vulnInfoService.save(vulnInfo);
}
......
由 WebSocket 调用的 getStatistics 方法用于前端显示扫描进度等信息,只进行查询操作,不涉及数据库保存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Async("connectorExecutor")
public CompletableFuture<JSONObject> getStatistics(Integer id) {
//手动注入bean,获取ScanService TargetService ScanRecordService
......
ScanRecordEntity entity = scanRecordService.getById(id);
//循环获取scanId scanSessionId
......
//获取扫描概况信息
JSONObject statistics = scanService.getStatistics(scanId, scanSessionId);
String status = statistics.getString("status");
JSONObject severity_counts = statistics.getJSONObject("severity_counts");
String progress = statistics.getJSONObject("scanning_app")
.getJSONObject("wvs").getJSONObject("main").getString("progress");
//将漏洞分布、扫描进度、扫描状态信息封装并返回
return CompletableFuture.completedFuture(result);
}
当用户调用生成报告功能后,系统先保存基本信息,然后调用 getReportStatus 方法监测报告生成状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Async("connectorExecutor")
public CompletableFuture<JSONObject> getReportStatus(String ReportId) {
//手动注入bean,ScanReportService ReportService ScanRecordService
......
ScanReportEntity entity = scanReportService.getByReportId(ReportId);
......
//获取报告状态
JSONObject report = reportService.getReport(entity.getReportId());
JSONArray download = report.getJSONArray("download");
//更新数据库
entity.setStatus(report.getString("status"));
entity.setDescription(report.getJSONObject("source").getString("description"));
entity.setHtmlUrl(download.getString(0).replace("/api/v1/reports/download/", ""));
entity.setPdfUrl(download.getString(1).replace("/api/v1/reports/download/", ""));
scanReportService.updateById(entity);
//将生成状态、报告描述信息封装返回
return CompletableFuture.completedFuture(result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
switch (jsonObject.getString("action")) {
case "getRecordStatus":
JSONObject status;
do {
CompletableFuture<JSONObject> getstatus = connectorService.getStatistics(jsonObject.getInteger("id"));
CompletableFuture.allOf(getstatus).join();
status = getstatus.get();
status.put("id", jsonObject.getInteger("id"));
this.sendMessage(status, session);
}while (!status.getString("status").equals("completed"));
break;
case "getReportStatus":
CompletableFuture<JSONObject> reportStatus = connectorService.getReportStatus(jsonObject.getString("reportId"));
CompletableFuture.allOf(reportStatus).join();
JSONObject report=reportStatus.get();
report.put("id", jsonObject.getInteger("id"));
this.sendMessage(report, session);
break;
case "HeartCheck":
heartCheck.put("HeartCheck", HeartCheck);
this.sendMessage(heartCheck,session);
break;