临近年末,公司又要举办“钓鱼演练”,之前钓鱼是以<a>标签链接的方式取诱导点击,想着员工对于“链接”多少有防备心了,故这次想换个花样。恰好朋友Y君不久前用了二维码钓鱼效果不错,索性就换成二维码吧!
钓鱼过程使用到的工具:
-
gophish
-
ewomail
有关工具搭建请见此处:
https://mp.weixin.qq.com/s?__biz=Mzg2MDYxOTQ1Mw==&mid=2247483923&idx=1&sn=02224bc45acc7a3d66c5d06d45a845dc&chksm=ce22d5c1f9555cd7ffc76c86476dc060e80e29e5663fbac1a6c8a46c5f1acde05bce207e8cce&token=914578584&lang=zh_CN#rd
下面正文开始:
由于gophish功能中不直接支持使用二维码钓鱼,所以有了下面的折腾:
-
首先想到的思路是将gophish生成的钓鱼URL链接,用在线二维码生成地址制作一个二维码,放入到邮件正文内发送
在线二维码生成器地址推荐:
https://www.hlcode.cn/url
但这样存在一个问题:二维码写死了,多人扫描同一二维码,人数少还好,如果是上百人的话,统计钓鱼数据会变得很困难,且数据展示也不方便.
2.故我想是否可以通过前端JS代码的方式,加载钓鱼URL链接后动态的请求在线二维码地址,生成二维码加载进邮件正文
<img id="qrCodeImage" alt="QR Code Image">
<!-- 页面加载完成时执行 generateQRCode() -->
<script>
document.addEventListener("DOMContentLoaded", function() {
generateQRCode();
});
async function generateQRCode() {
// 要生成二维码的网址(gophish的URL钓鱼链接)
const contentUrl = "http://example.com?rid=E1MDBFl";
// 构建生成二维码的URL
const apiUrl = `http://qrcode.hlcode.cn/beautify/style/create?bgColor=%23FFFFFF&bodyType=1&content=${encodeURIComponent(contentUrl)}&down=0&embedPosition=0&embedText=&embedTextColor=%23000000&embedTextSize=38&eyeInColor=%23000000&eyeOutColor=%23000000&eyeType=8&eyeUseFore=1&fontFamily=0&foreColor=%23000000&foreColorImage=&foreColorTwo=&foreType=0&frameColor=&gradientWay=0&level=H&logoShadow=0&logoShap=2&logoUrl=&margin=2&rotate=30&size=400&format=1&qrCodeId=0`;
try {
// 发送GET请求
const response = await fetch(apiUrl);
// 获取<img>元素
const qrCodeImage = document.getElementById("qrCodeImage");
// 设置<img>元素的src属性为获取到的二维码图片URL
qrCodeImage.src = await response.blob();
console.log("QR Code generated and displayed.");
} catch (error) {
console.error("An error occurred while processing the request:", error);
}
}
</script>
实验很完美,本地成功加载了请求的二维码图片,but。。。
实际测试,邮箱内空无一物?
百思不得其解后,谷歌告诉了答案:
为了安全性,所有的邮件无法执行JS脚本
3.看来只能从后端下手了,gophish使用的go语言,还好之前写过go的小工具嘿嘿,看了官方文档,模板引用如下:
这列模板引用的作用,就是可以在<钓鱼模板>或<钓鱼界面>内直接使用,gophish自动渲染解析,举个例子:
邮件发出后,此处的{{.URL}}会被渲染为gophish生成的钓鱼链接
so,既然没有二维码功能,那我们自己加一个吧,在结构体内添加EURL字段(二维码)
搜索URL找到有关模板功能的文件template_context.go:
可以看到这里定义上述模板引用字段的结构体,下面的
NewPhishingTemplateContext函数返回了该结构体对象
ps:还有一个坑点是outlook默认不加载远程图片,所以干脆在程序内base64编码写入邮件正文了。
修改后该文件代码如下:
package models
import (
"bytes"
"strings"
"net/mail"
"net/url"
"fmt"
"path"
"net/http"
"encoding/json"
"text/template"
"encoding/base64"
"io/ioutil"
)
type ApiResponse struct {
Code int `json:"code"`
Data string `json:"data"`
Msg string `json:"msg"`
}
// TemplateContext is an interface that allows both campaigns and email
// requests to have a PhishingTemplateContext generated for them.
type TemplateContext interface {
getFromAddress() string
getBaseURL() string
}
// PhishingTemplateContext is the context that is sent to any template, such
// as the email or landing page content.
type PhishingTemplateContext struct {
From string
URL string
Tracker string
TrackingURL string
EURL string
RId string
BaseURL string
BaseRecipient
}
// NewPhishingTemplateContext returns a populated PhishingTemplateContext,
// parsing the correct fields from the provided TemplateContext and recipient.
func NewPhishingTemplateContext(ctx TemplateContext, r BaseRecipient, rid string) (PhishingTemplateContext, error) {
f, err := mail.ParseAddress(ctx.getFromAddress())
if err != nil {
return PhishingTemplateContext{}, err
}
fn := f.Name
if fn == "" {
fn = f.Address
}
templateURL, err := ExecuteTemplate(ctx.getBaseURL(), r)
if err != nil {
return PhishingTemplateContext{}, err
}
// For the base URL, we'll reset the the path and the query
// This will create a URL in the form of http://example.com
baseURL, err := url.Parse(templateURL)
if err != nil {
return PhishingTemplateContext{}, err
}
baseURL.Path = ""
baseURL.RawQuery = ""
phishURL, _ := url.Parse(templateURL)
q := phishURL.Query()
q.Set(RecipientParameter, rid)
phishURL.RawQuery = q.Encode()
trackingURL, _ := url.Parse(templateURL)
trackingURL.Path = path.Join(trackingURL.Path, "/track")
trackingURL.RawQuery = q.Encode()
//这里x.x.x.x换成和钓鱼邮箱域名绑定的ip地址
ipMap := map[string]string{
"x.x.x.x": "www.example.com",
}
// 调用替换函数
newURL := replaceIPWithDomain(phishURL.String(), ipMap)
apiURL := "http://qrcode.hlcode.cn/beautify/style/create?bgColor=%23FFFFFF&bodyType=1&content=" + newURL + "&down=0&embedPosition=0&embedText=&embedTextColor=%23000000&embedTextSize=38&eyeInColor=%23000000&eyeOutColor=%23000000&eyeType=8&eyeUseFore=1&fontFamily=0&foreColor=%23000000&foreColorImage=&foreColorTwo=&foreType=0&frameColor=&gradientWay=0&level=H&logoShadow=0&logoShap=2&logoUrl=&margin=2&rotate=30&size=400&format=1&qrCodeId=0"
imageURL, err := GetQRCodeImageURL(apiURL)
base64DataURL, err := convertImageToBase64(imageURL)
return PhishingTemplateContext{
BaseRecipient: r,
BaseURL: baseURL.String(),
URL: phishURL.String(),
TrackingURL: trackingURL.String(),
EURL: base64DataURL,
Tracker: "<img alt='' style='display: none' src='" + trackingURL.String() + "'/>",
From: fn,
RId: rid,
}, nil
}
func convertImageToBase64(url string) (string, error) {
// 获取远程图片的内容
imageContent, err := fetchRemoteImage(url)
if err != nil {
return "", err
}
// 将图片内容转换为Base64编码
base64Image := base64.StdEncoding.EncodeToString(imageContent)
// 构造Base64格式的图片URL
base64DataURL := "data:image/png;base64," + base64Image
return base64DataURL, nil
}
func fetchRemoteImage(url string) ([]byte, error) {
// 发起HTTP请求获取远程图片内容
response, err := http.Get(url)
if err != nil {
return nil, err
}
defer response.Body.Close()
// 读取图片内容
imageContent, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
return imageContent, nil
}
func replaceIPWithDomain(url string, ipMap map[string]string) string {
for ip, domain := range ipMap {
// 将IP地址替换为域名
url = strings.Replace(url, ip, domain, -1)
}
return url
}
func GetQRCodeImageURL(apiURL string) (string, error) {
// 发送GET请求
response, err := http.Get(apiURL)
if err != nil {
return "", fmt.Errorf("Error making GET request: %v", err)
}
defer response.Body.Close()
// 检查响应是否成功 (状态码为 200-299)
if response.StatusCode < 200 || response.StatusCode >= 300 {
return "", fmt.Errorf("Error: Unexpected status code %s", response.Status)
}
// 解析JSON响应
var apiResponse ApiResponse
err = json.NewDecoder(response.Body).Decode(&apiResponse)
if err != nil {
return "", fmt.Errorf("Error decoding JSON: %v", err)
}
// 检查API响应中的错误码
if apiResponse.Code != 0 {
return "", fmt.Errorf("API returned an error: %s", apiResponse.Msg)
}
// 返回图片地址
return apiResponse.Data, nil
}
// ExecuteTemplate creates a templated string based on the provided
// template body and data.
func ExecuteTemplate(text string, data interface{}) (string, error) {
buff := bytes.Buffer{}
tmpl, err := template.New("template").Parse(text)
if err != nil {
return buff.String(), err
}
err = tmpl.Execute(&buff, data)
return buff.String(), err
}
// ValidationContext is used for validating templates and pages
type ValidationContext struct {
FromAddress string
BaseURL string
}
func (vc ValidationContext) getFromAddress() string {
return vc.FromAddress
}
func (vc ValidationContext) getBaseURL() string {
return vc.BaseURL
}
// ValidateTemplate ensures that the provided text in the page or template
// uses the supported template variables correctly.
func ValidateTemplate(text string) error {
vc := ValidationContext{
FromAddress: "[email protected]",
BaseURL: "http://example.com",
}
td := Result{
BaseRecipient: BaseRecipient{
Email: "[email protected]",
FirstName: "Foo",
LastName: "Bar",
Position: "Test",
},
RId: "123456",
}
ptx, err := NewPhishingTemplateContext(vc, td.BaseRecipient, td.RId)
if err != nil {
return err
}
_, err = ExecuteTemplate(text, ptx)
if err != nil {
return err
}
return nil
重新打包编译,运行
go build
./gophish
完美!测试结果如下:
客户端、网页端均可正常显示二维码,每人一码~
原文始发于微信公众号(哈拉少安全小队):二维码-邮件钓鱼历险记
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论