主要问题
HackerOne 接受任何关于在已公开报告视频中报告未公开报告的报告。 这听起来有点令人困惑,所以我会举一个例子来更清楚地解释这一点。
让我们以这个公开报告为例:#1294767:https://hackerone.com/reports/1294767:
该报告包含一个漏洞猎人录制的视频,作为其报告的概念验证(PoC);问题在于,该视频中显示了未公开的报告(在左侧)。
一个想法
我们需要一种方法来扫描平台上所有已公开报告中的视频。如果以传统方式手动查看每个视频,可能需要数周时间,因此我们需要找到一种更高效的方法来完成这项任务。
通过借助人工智能和计算机视觉,我们可以找到解决这一问题的方法。因此,第一步是确定如何训练我们的 AI 模型。
AI 模型方法
我最初想到的解决方案是使用图像分类方法,但我认为这并不是该问题的最佳选择,因此我决定使用目标检测来解决问题。 为了更清楚地说明这些模型的区别,我将举一个例子:
图像分类模型: 图像分类模型的设计是以视频帧为输入,并将其分类为“包含未公开报告”或“不包含未公开报告”。然而,问题出现在数据集收集阶段,因为获取足够数量的“NOT”类(即不包含公开报告的帧)可能很困难。负样本的稀缺可能会阻碍模型的训练过程,并影响其整体性能。
目标检测模型: 另一方面,目标检测模型通过分析视频帧,识别其经过训练的特定对象。与将整个帧进行分类不同,目标检测模型基于训练数据定位并识别帧中的单个对象。
在数据集方面,我决定收集250张截图,于是我打开了我的 HackerOne 账户和我朋友的账户,开始截取截图。
我选择了这部分作为目标(红色矩形),因此当模型在视频中看到这部分时,它就会检测到它。
由于截图的方式相同且屏幕内容不会变化,标注框的坐标在所有图像中都不会发生变化。 我打开了 labelImg
(一个用于标注目标的 Python 工具),只标注了一张图片,然后编写了一个小脚本,将这张图片的标注信息复制到所有图片中。
import os
image_folder_path = './images'
text_folder_path = './train/labels'
image_file_list = os.listdir(image_folder_path)
coordinates = "0 0.170047 0.550314 0.254717 0.798742"
for image_file_name in image_file_list:
file_basename = os.path.splitext(image_file_name)[0]
text_file_name = file_basename + '.txt'
text_file_path = os.path.join(text_folder_path, text_file_name)
with open(text_file_path, 'w') as text_file:
text_file.write(coordinates)
边界框(坐标)由五个值表示:[object-class, x_center, y_center, width, height]
。
object-class
表示边界框中目标的类别标签,通常用一个整数值来表示,对应于特定的类别或类型。
x_center
和 y_center
是边界框中心点的归一化坐标。为了归一化坐标,我们将边界框中心点在 x 轴和 y 轴上的像素值分别除以图像的宽度和高度。
width
和 height
表示边界框的宽度和高度,同样经过归一化处理。
labelImg
为每张图片创建一个文本文件,并将标注框的坐标保存在该文件中。因此,我将坐标值存储在 coordinates
变量中,并读取图片名称,然后创建一个以图片名称命名的文本文件,其中包含坐标的值。
我使用基于 YOLO[1] 原理的目标检测算法训练了我的模型。
YOLOv7[2] 是由台湾中研院 AI 实验室开发的实时目标检测算法,是 YOLO(You Only Look Once)目标检测算法的第七个版本,也是当前最精确且高效的目标检测算法之一。 YOLOv7 是一个单阶段目标检测器,意味着它可以通过对图像的一次处理完成目标检测。这使得它比诸如 Faster R-CNN 等双阶段检测器快得多。同时,YOLOv7 的精度也很高,在各种数据集上的表现优于其他目标检测算法。
该模型在训练和测试中表现良好,但在检测不包含公开报告的视频时产生了许多误报,并且在检测包含漏洞的视频方面表现也不理想。
这是一个误报结果的示例;模型以高置信度将这一部分视为公开报告。 因此,我必须找到另一种更优的方法来改进模型,使其在检测方面表现更好。
我更改了标注的方式,现在变成这样:
在这里,我必须手动进行标注,因为每个框的大小和位置都不同。
我认为这种方法的表现会更好,事实证明确实如此。模型训练后,误报减少了,检测效果也有所提升。
查找视频
现在,我们需要找到一种方法来下载所有公开报告中的视频。 我开始寻找是否可以以 JSON 格式获取报告信息,以便更轻松地处理数据。我想起报告页面中有一个导出功能,但当我点击时,发现并没有 JSON 格式,而是 pdf、zip 和纯文本格式。我选择了 pdf 以查看其 URL 格式是什么样的。结果发现,URL 只是将报告编号的末尾添加了 .pdf
:
https://hackerone.com/reports/1294767.pdf
我迅速将 .pdf
更改为 .json
,果然,报告现在以 JSON 格式显示。
接下来,我需要编写一个 Python 脚本,从所有公开报告中下载视频。
但首先,我们需要获取所有公开报告的编号。我搜索了一段时间,看看是否有人收集了这些报告,最终在 GitHub 上找到了这个有用的 repo[3]。他还上传了一个 Python 脚本,可以用于获取最新的报告数据。
现在开始工作……
开始利用!
首先,我们需要从 CSV 文件中提取报告编号,因此我编写了一个实用的脚本,用于提取这些编号并将其保存到一个文本文件中。
import csv
csv_file = 'data.csv' # The csv file in the repo
output_file = 'report_numbers.txt'
with open(csv_file, 'r') as file:
reader = csv.reader(file)
next(reader)
numbers = [row[2].split('/')[-1] for row in reader if int(row[2].split('/')[-1])]
numbers.sort(reverse=True)
with open(output_file, 'w') as file:
file.write('n'.join(numbers))
之后,我编写了另一个脚本,用于从报告中下载所有视频。
import requests
import json
import os
if not os.path.exists('videos'):
os.makedirs('videos')
with open('report_numbers.txt', 'r') as file:
report_numbers = file.readlines()
i = 0
for report_number in report_numbers:
i = i + 1
report_number = report_number.strip()
endpoint = f"https://hackerone.com/reports/{report_number}.json"
response = requests.get(endpoint)
print(f"{i}/{len(report_numbers)}")
if response.status_code == 404:
continue
if response.status_code == 200:
data = response.json()
if 'attachments' in data:
attachments = data['attachments']
for attachment in attachments:
if 'video' in attachment['type']:
file_name_temp = attachment['file_name']
file_name = file_name_temp.split('.')[0]
file_ext = file_name_temp.split('.')[-1]
new_file_name = f"{report_number}__{file_name}.{file_ext}"
video_url = attachment['expiring_url']
video_response = requests.get(video_url)
if video_response.status_code == 200:
print("Video has been downloaded!")
video_path = os.path.join('videos', new_file_name)
with open(video_path, 'wb') as video_file:
video_file.write(video_response.content)
if 'activities' in data:
activities = data['activities']
for activity in activities:
if 'attachments' in activity:
attachments = activity['attachments']
for attachment in attachments:
if 'video' in attachment['type']:
file_name_temp = attachment['filename']
file_name = file_name_temp.split('.')[0]
file_ext = file_name_temp.split('.')[-1]
new_file_name = f"{report_number}__{file_name}.{file_ext}"
video_url = attachment['url']
video_response = requests.get(video_url)
if video_response.status_code == 200:
print("Video has been downloaded!")
video_path = os.path.join('videos', new_file_name)
with open(video_path, 'wb') as video_file:
video_file.write(video_response.content)
让我们解释一下这个脚本是如何工作的。首先,我们有两种类型的视频:第一种是你写报告时上传的视频,这个视频会出现在 data['attachments']['expiring_url']
中,第二种视频是上传在评论中的,这个会出现在 data['activities']['attachments']['url']
中。
注意,data
是整个 JSON 响应。
一个重要的事情是将下载的视频与报告编号关联起来,所以当我下载视频时,我会将其重命名为 {报告编号}__{原始视频名称}.{视频扩展名}
。
第二部分是将这些视频传递给我的模型,但问题是视频数量很多,模型需要很长时间才能检测到包含漏洞的视频,因为它需要扫描视频中的每一帧。 我想到一个主意。如果我将视频帧率减少到每秒仅三帧呢?这个数字足够用来检测视频中是否包含未公开报告。
减少帧数将节省我们 10 倍的时间,假设一段视频的平均帧率为每秒 30 帧,如果我们不减少帧率,模型需要的时间会更长。
还有一个问题:YOLOv7 无法处理 webm 格式的视频,因此我必须使用 ffmpeg[4] 将这种类型的视频转换为 mp4。我编写了一个小的 Python 脚本来自动化这些任务(将 webm 视频转换为 mp4 并减少帧数)。
import os
import subprocess
folder_path = "./videos"
def reduce_frames(input_file, output_file):
subprocess.call(['ffmpeg', '-i', input_file, '-r', '3', output_file])
def convert_folder_videos(folder_path):
i = 0
total = len(os.listdir(folder_path))
for filename in os.listdir(folder_path):
i = i + 1
print(f"{i}/{total}")
if filename.endswith(".webm"):
try:
input_file = os.path.join(folder_path, filename)
output_file = os.path.join(folder_path, os.path.splitext(filename)[0] + ".mp4")
reduce_frames(input_file, output_file)
print(f"Converted {input_file} to {output_file}")
os.remove(input_file)
except:
continue
else:
try:
input_file = os.path.join(folder_path, filename)
output_file = os.path.join(folder_path, os.path.splitext(filename)[0] + "__reduced" + os.path.splitext(filename)[-1])
reduce_frames(input_file, output_file)
print(f"Converted {input_file} to {output_file}")
os.remove(input_file)
except:
continue
convert_folder_videos(folder_path)
现在我们准备好将视频传递给我们的模型了。
python detect.py --weights best.pt --conf 0.50 --img-size 640 --source ./video-scan/videos/ --name new-scan
运行此命令后,模型将保存检测到对象(未公开报告)的视频。
由于存在误报结果,我们必须手动查看这些视频,直到能够确定它们是否真的包含未公开的报告。
最终,模型检测到了几十个视频,但其中一些视频并不包含未公开报告(视频中显示的唯一报告是已经公开的报告,即我们当前下载的视频),这是其中一个示例:
我提交了近 15 个报告给 HackerOne,但不幸的是,他们决定不支付这些报告的奖励。在我报告之后,他们声明这类报告将不再被考虑,并将我的所有报告标记为信息性报告关闭。此外,他们通知我,他们之前误支付了类似概念的报告奖励。
最终我们将此案件上报给了 HackerOne 的联合创始人 Jobert Abma,他们决定支付所有报告的奖励。
References
[1]
YOLO: https://www.v7labs.com/blog/yolo-object-detection
[2]
YOLOv7: https://github.com/WongKinYiu/yolov7
[3]
repo: https://github.com/reddelexc/hackerone-reports
[4]
ffmpeg: https://github.com/FFmpeg/FFmpeg
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。
原文始发于微信公众号(白帽子左一):通过计算机视觉帮助发现隐藏的漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论