序言
大多数攻击者或者钓鱼演练需要进行钓鱼邮件时似乎使用的都是gophish钓鱼平台,但是该平台源代码仅对邮件中的链接进行的替换,无法替换钓鱼邮件中的二维码。由于业务需要,在进行部分代码审计后,完成了对钓鱼邮件中二维码替换功能的添加。当然,这仅仅是个人的思路,如果大家有别的想法,可以来沟通沟通。
想法
在进行钓鱼页面导入时,是需要导入模板的,检索模板时发现,其在导入模板后对页面中的链接字段进行了字段替换,将链接替换成{{.URL}}字段,那么其中必然一个方法通过锚定一个受害者邮箱的方式生成对应的rid和钓鱼页面。从gophish的功能来看是,我们点击Launch Campaign这个按钮时生成的。
那么我们是不是可以从{{.URL}}替换这个角度来进行追踪呢?在源码中,进行链接字段替换时并没有识别图片链接字段,
1.如果我们先实现对模板中对图片的识别功能,将钓鱼邮件模板中的图片替换成{{.URL}}
2.在点击Launch Campaign时,增加一个识别方法判断发出的是钓鱼二维码还是钓鱼链接,如果是钓鱼链接
则使用原来方法,如果是钓鱼二维码则使用我们自定义的二维码生成和二维码访问链接替换{{.URL}}
3.增加路由方法,使公网可以访问我们生成的二维码
代码审计
考虑到代码量的问题,并没有对代码进行全局检索,而是根据需求和思路进行指定的代码审计,当然我的实力也不够的。
1.模板中{{.URL}}字段识别与替换
既然代码是识别模板中的链接字段替换成{{.URL}}字段,我们是不是可以通过搜索{{.URL}}找到该方法呢?
搜索{{.URL}}字段发现
排除掉测试文件和js文件,我们发现import.go存在这个方法
controllers/api/import.go
发现ImportEmail方法是通过下方代码识别与替换的
d.Find("a").Each(func(i int, a *goquery.Selection) {
a.SetAttr("href", "{{.URL}}")
})
简述一下上述代码功能
1.使用goquery库来遍历文档中的所有"a"元素,并将它们的"href"属性设置为"{{.URL}}"。这个操作可以用于修改HTML文档中的链接。
2.在代码中,d.Find("a")用于选择所有的"a"元素,然后使用.Each()方法遍历每个选中的元素。
3.在遍历的过程中,使用a.SetAttr("href", "{{.URL}}")来设置每个"a"元素的"href"属性为"{{.URL}}"
那我们直接修改一下遍历识别img元素,替换img元素中src字段的图片链接识别为{{.URL}}
//二维码替换功能
d.Find("img").Each(func(i int, img *goquery.Selection) {
img.SetAttr("src", "{{.URL}}")
})
2.锚定收件人生成对应的rid和模板中链接替换
既然有rid这个关键字那就搜索
排除掉非红框中检索出来五个正常的go文件,那考虑一下rid的生成,那不可能是int64,只剩下template_context.go和maillog.go了,考虑到文件名字,先看这个。
models/templete_context.go
// 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
RId string
BaseURL string
BaseRecipient
}
发现这个是接受模板的结构体
那查看一下哪些方法调用了这个结构体
发现了一个方法名newPhishingTemplateContext(),就在结构体下面
// 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()
return PhishingTemplateContext{
BaseRecipient: r,
BaseURL: baseURL.String(),
URL: phishURL.String(),
TrackingURL: trackingURL.String(),
Tracker: "<img alt='' style='display: none' src='" + trackingURL.String() + "'/>",
From: fn,
RId: rid,
}, nil
}
哦?不得了 找到了
这是接受模板,rid字符串,返回一个修改后的模板内容的方法。
那我们先仿照这个写个替换图片的方法。
func NewPhishingTemplateContextImg(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
// 对于基本 URL,我们将重置路径和查询.这将以 http://example.com 的形式创建一个 URL
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()
fishUrl := phishURL.String()
// 获取当前文件的绝对路径0
dir, err := filepath.Abs(os.Args[0])
if err != nil {
//fmt.Println("获取当前文件路径时发生错误:", err)
return PhishingTemplateContext{}, err
}
// 获取上级目录
dir0 := filepath.Dir(dir)
//fmt.Println("上上级目录:", parentDir)
//fmt.Println("0")
//构造父路径的文件
dir1 := dir0 + "/qrcodefile"
//判断当前路径下/qrcodefile是否存在
if _, err := os.Stat(dir1); os.IsNotExist(err) {
//fmt.Println("不存在文件")
//如果不存在,则使用 os.Mkdir 函数创建该目录。 赋予权限0755
os.Mkdir(dir1, 0755)
}
//此时当前文件夹下存在/qrcodefile
//在绝对路径/qrcodefile 下创建二维码图片
//拼接图片路径
//绝对路径创建图片存储文件夹
//saveDir := "/qrcodefile/"
//图片名字指定为rid名字
saveFileName := rid + ".png"
//文件路径名
fullPath := dir1 + "/" + saveFileName
//在绝对路径/qrcodefile目录下生成rid.png的二维码图片
// 保存二维码图片到指定目录
if err := generateQRCode(fishUrl, fullPath); err != nil {
return PhishingTemplateContext{}, err
}
//该图片访问链接
fishImgUrl, _ := url.Parse(templateURL)
fishImgUrl.Path = path.Join(fishImgUrl.Path, "/qrcodefile/") //url增加/tracke
imgName := rid + ".png"
fishImgUrl.Path = path.Join(fishImgUrl.Path, imgName)
//http://124.223.97.10:81/qrcodefile/3DRAYUveO.png
//fishUrl1 := "https://demo.haoapp.top:60080/api/qr_code/1/download?email_id=1&task_id=63&scene=scan"
fishUrl1 := fishImgUrl.String()
trackingURL, _ := url.Parse(templateURL)
trackingURL.Path = path.Join(trackingURL.Path, "/track") //url增加/tracke
trackingURL.RawQuery = q.Encode()
//对模板的内容进行对应字符串更改
return PhishingTemplateContext{
BaseRecipient: r,
BaseURL: baseURL.String(),
//URL: fishImgUrl.String(),
//URL: phishURL.String(),
URL: fishUrl1,
TrackingURL: trackingURL.String(),
Tracker: "<img alt='' style='display: none' src='" + trackingURL.String() + "'/>",
From: fn,
RId: rid,
}, nil
}
以及对应的针对指定钓鱼链接url生成图片以及存放文件的方法
func generateQRCode(url string, outputPath string) error {
// 创建二维码图片
qrCode, err := qrcode.New(url, qrcode.Medium)
if err != nil {
return err
}
// 打开输出文件
file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()
// 将二维码图片写入文件
err = png.Encode(file, qrCode.Image(256))
if err != nil {
return err
}
return nil
}
创建二维码存储文件部分
// 获取当前文件的绝对路径0
dir,