Skip to content

使用Zeek解析POP3协议(2)

  • by

本文根据对Zeek的POP3 API的测试结果,初步归纳了一个pattern,编写Zeek脚本,输出POP3的解析日志。

我们知道,POP3协议请求获取邮件的的命令是RETR,而根据使用Zeek解析POP3协议(1)对Zeek API的测试结果,可以初步归纳一个解析邮件内容的pattern:

  1. 邮件起始标志:
    1. client发出RETR命令,请求获取某封邮件;
    2. server响应OK
  2. 邮件内容:多行字符串
  3. 邮件结束标志:遇到一个新的命令

其中起始标志的2项判断条件都要满足:RETR请求获取邮件,OK代表响应请求成功,而在遇到下一个请求命令之前,我们认为之间的多行字符串都是邮件内容。因此解析邮件实质上是在API输出的多行sting中逐行跟踪分析以上pattern。

首先创建一个用于保存单封邮件信息的record Msg

type Msg: record {
    ts:                 time;
    uid:                string;
    id:                 conn_id;
    flag_retr_succ:     bool;       # 标识retr请求是否获取邮件内容成功
    request:            string;         # 请求命令
    req_arg:            string;         # 请求命令参数
    reply:              string;         # 响应
    retr_data_linenum:  int;                # 邮件内容的字符串行数
    retr_data:          vector of string;   # 保存邮件内容
};

其中flag_retr_succ用于标识邮件起始标志2项条件是否符合。

1. 分析邮件起始

pop3_request追踪retr命令:

event  pop3_request(c: connection, is_orig: bool, command: string, arg: string)
{
        ...
    if(command == "RETR") {
        local retr_msg: Msg = [ts = network_time(),id = cid,uid = cuid,flag_retr_succ = F, 
                                    request = "RETR",req_arg = arg, 
                                    reply = "",retr_data_linenum = 0, 
                                    $retr_data = vector()];
        g_retr_msg = retr_msg;
    }
  ...
}

监测到retr命令后,初始化一个全局的Msg

相应地,在pop3_reply分析服务器是否响应OK

event pop3_reply(c: connection, is_orig: bool, cmd: string, msg: string)
{
    ...
    if(cmd == "OK" && g_retr_msg?flag_retr_succ) { # g_retr_msg exists
        if(g_retr_msgrequest == "RETR" && g_retr_msgreply == "") {
            g_retr_msgflag_retr_succ = T;
            g_retr_msg$reply = "OK";
        }
    }
}

如果发现:

  1. reply命令为OK
  2. 全局msg中记录的request命令是retr,标志flag_retr_succ为False,且reply为空

说明此reply响应的是一个retr的请求命令,获取邮件成功。至此邮件起始的2项条件满足:



2. 保存邮件内容

邮件起始之后的多行字符串默认为邮件内容,使用pop3_data保存邮件内容:

event  pop3_data(c: connection, is_orig: bool, data: string)
{
    if(g_retr_msg?flag_retr_succ && g_retr_msgflag_retr_succ == T) {
        if(g_retr_msgretr_data_linenumretr_data += data;
            g_retr_msg$retr_data_linenum += 1;
        }
    }
}

监测到flag_retr_succ为True,即此时data的内容都是获取的邮件信息,保存到字符串vector中再逐行解析。

分析邮件获取结束并解析邮件内容

同样使用pop3_request追踪邮件结束:

event pop3_reply(c: connection, is_orig: bool, cmd: string, msg: string)
{
    ...
    pop3_proc_g_retr_msg(); # 监测邮件结束标志并解析
    if(cmd == "OK" && g_retr_msg?$flag_retr_succ) { # g_retr_msg exists
        ...
    }
    ...
}

其中pop3_proc_g_retr_msg:

function pop3_proc_g_retr_msg()
{
    if(g_retr_msg?flag_retr_succ && g_retr_msgflag_retr_succ == T) {
        # 更新pop3 info信息
        local rec: POP3::Info = [ts = g_retr_msgts, 
                                 uid = g_retr_msguid, 
                                 id = g_retr_msgid];
        g_pop3_rec = rec;
        ...

        # 解析邮件内容
        for(idx in g_retr_msgretr_data) {
            # print g_retr_msgretr_data[idx];
            local data:string = g_retr_msg$retr_data[idx];
            local key: string = "";
            local val: string = "";
            local len: int;
            if(data != "") {
                # 匹配to字段
                if(/^[tT][oO]:/ in data) {
                    key = "to";
                    val = data[3:];
                }
                else if(/^[fF][rR][oO][mM]:/ in data) {
                    key = "from";
                    val = data[6:];
                }
                ...
            }
            if(key != "" && val != "")
                pop3_update_g_rec_data(key, val);
        } 
        # 写入pop3日志
        Log::write(POP3::LOG, g_pop3_rec);

        # 解析一封邮件结束,重新初始化全局的msg
        ...
    }
}

该函数在pop3_requrest被调用,并且判断全局msg中flag_retr_succ是否为T,如果T说明遇到了一个新的命令,即邮件获取完成:



之后解析保存在msg中的邮件内容,并更新pop3的info信息(参考Zeek默认的smtp解析脚本,POP3脚本也创建了一个类似的info record,用于将解析结果写入日志)。

解析保存的邮件内容字符串,同样参考Zeek smtp的解析脚本,使用正则匹配邮件关键字段,例如from, to等。

3. 脚本解析结果

用以上思路编写解析脚本,在测试邮件上的解析结果:

$ cat pop3.log | jq
{
“ts”: 1615003258.432899,
“uid”: “CeJci2byiawb4zZlk”,
“id.orig_h”: “192.168.153.18”,
“id.orig_p”: 39118,
“id.resp_h”: “192.168.153.19”,
“id.resp_p”: 110,
“command”: [
“RETR”,
“OK”
],
“arg”: [
“2”
],
“date”: ” Fri, 5 Mar 2021 23:00:37 -0500″,
“from”: “lisi lisi@localdomain.com“,
“to”: [
” zhangsan@localdomain.com”
],
“msg_id”: ” 7ea7b5a3-3e76-ceee-2a49-a9ab81d5cc4c@localdomain.com“,
“subject”: ” This is a test mail”,
“user_agent”: ” Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101″
}

可以看到,对比使用Zeek解析POP3协议(1)中测试邮件的内容,该脚本解析了邮件内容的相应字段信息。如果需要解析更多字段,增加类似的正则匹配即可。

该脚本只是基于一个比较粗糙的pattern,一些细节可能尚未考虑到。同时也有一些问题:

  1. 有的邮件字段信息,例如测试邮件样例中的UA跨行了,即保存在多行的字符串中,应如何处理。
  2. 如何解析邮件附件等。
Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *