加入新公司有一段时间啦,终于有时间来更新更新一下文章了,这篇文章不同于以往的渗透实战哈。阔能没有那么鸡动人心,话不多说,直接上正文。
使用github actions来配置自动化dast扫描(当然也阔以用定时任务的方式,方法是活的),这里就不前置讲github actins了,有不了解的阔以看(https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions)。
这里实现主要分成3个step,分别是执行dast扫描(这里我选择的是nuclei)、将扫描结果转换成报告、钉钉提醒扫描结束。
执行dast扫描
action代码如下,首先下载nuclei到action的运行环境,然后运行nuclei对目标进行扫描。
- name: Download Nuclei
run: |
# 下载 Nuclei
wget https://github.com/projectdiscovery/nuclei/releases/download/v3.3.5/nuclei_3.3.5_linux_amd64.zip
# 解压缩
unzip nuclei_3.3.5_linux_amd64.zip -d /usr/local/bin/
# 赋予执行权限
chmod +x /usr/local/bin/nuclei
- name: Run Nuclei
run: |
# 使用如下出口 IP 地址执行 Nuclei 扫描
EXTERNAL_IP=$(curl -s https://api.ipify.org)
echo "External IP is: $EXTERNAL_IP"
nuclei -list=urls.txt -je report.json -rl 80
这里有一步,扫描结束后需要将代码upload,不然下一步python转换无法获取到扫描结果,action代码如下。
- name: Upload report.json
uses: actions/upload-artifact@v3
with:
name: nuclei-report
path: |
report.json
生成报告
同样的使用action获取python需要的依赖,然后运行python脚本,运行的脚本如下(简单写的一个小脚本,输出有棱有角就行,不要太在意最终的美观性哈)。
import json
import re
from datetime import datetime
from docx import Document
from docx.shared import RGBColor
# 指定输入和输出文件
input_file = 'report.json' # 输入的 Nuclei JSON 文件
url_file = 'urls.txt' # 扫描的地址
output_file = 'vulnerability_report.docx' # 输出的 DOCX 报告文件
now = datetime.now()
# 读取 Nuclei 生成的 JSON 文件
try:
with open(input_file, 'r', encoding='utf-8') as f:
results = json.load(f) # 解析 JSON 文件
except FileNotFoundError:
print(f"错误: 找不到文件 {input_file}。请确保该文件存在。")
exit(1)
except json.JSONDecodeError:
print(f"错误: 文件 {input_file} 不是有效的 JSON 格式。")
exit(1)
# 创建 DOCX 文件
doc = Document()
# 添加标题
doc.add_heading('Core Service Application Vulnerability Scan Report', 0)
# 添加表格:记录测试人员和测试时间
doc.add_paragraph("")
doc.add_heading('Scanning Plan', level=1)
table = doc.add_table(rows=1, cols=2)
table.style = 'Table Grid'
hdr_cells = table.rows[0].cells
hdr_cells[0].text = 'Scan By'
hdr_cells[1].text = 'Scan Date'
row_cells = table.add_row().cells
row_cells[0].text = 'Chen Guo' # 你可以在此处替换为实际测试人员
row_cells[1].text = datetime.now().strftime("%Y-%m-%d")
doc.add_heading('Scanning Tool', level=1)
doc.add_paragraph(f"nuclei")
# 读取 URL 文件,获取测试地址
try:
with open(url_file, 'r', encoding='utf-8') as f:
urls = [line.strip() for line in f.readlines() if line.strip()] # 读取并去掉空行
except FileNotFoundError:
print(f"错误: 找不到文件 {url_file}。请确保该文件存在。")
exit(1)
# 插入URL表格
doc.add_heading('Scan Urls', level=1)
# 创建一个新表格来显示 URL 地址
url_table = doc.add_table(rows=1, cols=1)
url_table.style = 'Table Grid'
hdr_cells = url_table.rows[0].cells
hdr_cells[0].text = 'Scan URL'
# 填充 URL 地址
for url in urls:
row_cells = url_table.add_row().cells
row_cells[0].text = url
# 统计高、中、低风险漏洞数
high_risk_count = 0
medium_risk_count = 0
low_risk_count = 0
def extract_header_from_response(response):
"""
从响应字符串中提取 HTTP header。
:param response: 包含响应内容的字符串
:return: 提取的header部分
"""
# 假设header部分以"HTTP/1.1"开始,直到空行
header_match = re.match(r'^(.*?)(r?nr?n)', response, re.DOTALL)
if header_match:
return header_match.group(1) # 返回header部分
return'No header found'
# 检查是否有结果
if not results:
doc.add_paragraph("没有找到漏洞扫描结果。")
doc.save(output_file)
exit(0)
# 遍历结果并格式化输出
for result in results:
result = json.dumps(result)
result = json.loads(result)
severity = result.get('info', {}).get('severity')
if severity != 'info' and severity != 'unknown':
# 更新统计计数
if severity == 'high':
high_risk_count += 1
elif severity == 'medium':
medium_risk_count += 1
elif severity == 'low':
low_risk_count += 1
# 先输出漏洞统计信息:使用表格展示
doc.add_heading('Vulnerability Summary', level=1)
# 添加一个表格来展示统计数据
vuln_table = doc.add_table(rows=1, cols=3)
vuln_table.style = 'Table Grid'
hdr_cells = vuln_table.rows[0].cells
hdr_cells[0].text = 'Risk Level'
hdr_cells[1].text = 'Count'
hdr_cells[2].text = 'Description'
# 填充表格内容
vuln_row = vuln_table.add_row().cells
run_red = vuln_row[0].paragraphs[0].add_run('High')
run_red.font.color.rgb = RGBColor(255, 0, 0)
vuln_row[1].text = str(high_risk_count)
vuln_row[2].text = 'High risk vulnerabilities'
vuln_row = vuln_table.add_row().cells
run_yellow = vuln_row[0].paragraphs[0].add_run('Medium')
run_yellow.font.color.rgb = RGBColor(255, 165, 0)
vuln_row[1].text = str(medium_risk_count)
vuln_row[2].text = 'Medium risk vulnerabilities'
vuln_row = vuln_table.add_row().cells
run_green = vuln_row[0].paragraphs[0].add_run('Low')
run_green.font.color.rgb = RGBColor(0, 255, 0)
vuln_row[1].text = str(low_risk_count)
vuln_row[2].text = 'Low risk vulnerabilities'
doc.add_paragraph("") # 添加空行分隔统计信息和漏洞详情
doc.add_heading('Vulnerability Details', level=1)
# 再输出漏洞详细信息
num = 1
for result in results:
result = json.dumps(result)
result = json.loads(result)
severity = result.get('info', {}).get('severity')
doc.add_heading(f"{num}. {result.get('info', {}).get('name', 'N/A')}", level=2)
host = result.get('host', 'N/A').replace('n', '')
paragraph = doc.add_paragraph(f"Target: {host}")
paragraph.paragraph_format.space_before = 0
paragraph.paragraph_format.space_after = 0
vulnerability_name = result.get('info', {}).get('name', 'N/A').replace('n', '')
paragraph = doc.add_paragraph(f"Vulnerability Name: {vulnerability_name}")
paragraph.paragraph_format.space_before = 0
paragraph.paragraph_format.space_after = 0
p = doc.add_paragraph()
if severity == 'high':
run = p.add_run(f"Severity: high")
run.font.color.rgb = RGBColor(255, 0, 0) # 红色
elif severity == 'medium':
run = p.add_run(f"Severity: medium")
run.font.color.rgb = RGBColor(255, 165, 0) # 橙色
elif severity == 'low':
run = p.add_run(f"Severity: low")
run.font.color.rgb = RGBColor(0, 255, 0) # 绿色
else:
run = p.add_run(f"Severity: info")
run.font.color.rgb = RGBColor(0, 0, 255) # 蓝色
p.paragraph_format.space_before = 0
p.paragraph_format.space_after = 0
description = result.get('info', {}).get('description', 'N/A').replace('n', '')
paragraph = doc.add_paragraph(f"Description: {description}")
paragraph.paragraph_format.space_before = 0
paragraph.paragraph_format.space_after = 0
vulnerability_url = result.get('matched-at', 'N/A').replace('n', '')
paragraph = doc.add_paragraph(f"Vulnerability URL: {vulnerability_url}")
paragraph.paragraph_format.space_before = 0
paragraph.paragraph_format.space_after = 0
# 添加请求和响应信息
request = result.get('request', 'no request') # 获取原始请求
response = result.get('response', 'no response') # 获取原始响应
if response != 'no response':
response = extract_header_from_response(response)
paragraph = doc.add_paragraph("Request:")
paragraph.paragraph_format.space_before = 0
paragraph.paragraph_format.space_after = 0
paragraph = doc.add_paragraph()
for line in request.splitlines():
run = paragraph.add_run(line)
run.add_break() # 添加换行符
paragraph = doc.add_paragraph("Response:")
paragraph.paragraph_format.space_before = 0
paragraph.paragraph_format.space_after = 0
paragraph = doc.add_paragraph()
for line in response.splitlines():
run = paragraph.add_run(line)
run.add_break() # 添加换行符
num = num + 1
# 如果没有找到漏洞
if num == 1:
doc.add_paragraph("No risk vulnerabilities have been found, only info information has been found so far")
# 保存 DOCX 文件
doc.save(output_file)
print(f"漏洞报告已生成: {output_file}")
推送报告信息
这里使用的钉钉机器人推送扫描结束通知,github action代码如下。当然也阔以直接将报告发送到邮箱之类的。
- name: dingtalk-bot
uses: leafney/dingtalk-action@v1
if: always()
env:
DINGTALK_ACCESS_TOKEN: ${{ secrets.DINGTALK_ACCESS_TOKEN }}
with:
msgtype: link
title: 'Core Service Vulnerability Scan'
text: 'Vulnerability Scan 已经结束:请前往github action下载report。'
msg_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
查看效果,如下。
原文始发于微信公众号(安全无界):自动化Dast扫描+报告输出
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论