上篇文章中提出了一个基于docker的蜜罐系统设计,仅仅是将攻击者的流量引入到蜜罐中,但这是不够的,我们要发起反击,将攻击的流量打回去!为什么想这么做呢?记得很久以前参加某个安全沙龙的时候,某个安全专家提出了一个十分猥琐的思路,大意是,如果你发现有一个IP在持续的对你的SSH(以SSH为例,其他服务类似)进行爆破,有一种可能是这个IP被别人种马并自动进行对其他主机的爆破,以便传播自己,在这种情况下,爆破所用的字典很大可能上是固定的,也就是说,你用这个IP爆破过来的流量反爆破回去,很有可能爆破成功XD,于是很自然的就想将这个思路用到蜜罐上了。

0x00 初识PAM

以前也尝试过写通过某些很挫的方法记录用户SSH登录的账号密码,但是奈何太菜了,只能记录正确的账号密码,那些爆破失败的密码是没有办法记录的。最近知道了PAM这个东西,工作中也在使用,于是学习了一番。

PAM的全称是Pluggable Authentication Modules for Linux,大概就是一个插件化的认证模块,每个程序可以通过配置文件调用不同的认证模块,以便于达到认证的目的。先排出两个参考资料,已经讲的十分详细了:
The Linux-PAM Guides
Using Pluggable Authentication Modules (PAM)

0x01 PAM配置文件

PAM的配置文件一般放在/etc/pam.d/文件夹中,例如login程序的PAM配置文件为/etc/pam.d/login
PAM配置文件有固定的格式,一般如下:

module_interface     control_flag     module_name module_arguments

module_interface代表的是认证过程中不同的阶段,目前总共有四种:

  • auth: 认证使用,例如验证密码是否正确。
  • account: 验证本次访问是否被允许,例如检查账户是否超过了有效期或账户是否允许在某个时间登录。
  • password: 修改用户密码时使用。
  • session: 配置、管理用户的session,例如挂载用户根目录时之类的操作。

每个PAM的模块都可以提供多种interface,例如pam_unix.so就可以提供上面四种所有的interface。
例如下面这个配置,则是将pam_unix.so配置到auth interface。

auth    required    pam_unix.so

模块的interface指令是可以被堆叠的(原文: stacked),例如reboot的配置:

[root@VM-UBUNTU ~]# cat /etc/pam.d/reboot
#%PAM-1.0
# pam_rootok用于检查当前用户是否为root,检查的方法是查看UID是否为0。如果检查成功,则直接返回成功,不会进行其他的检查。
auth        sufficient  pam_rootok.so
# pam_console.so模块用于认证用户,如果用户登录到console中了,会检查/etc/security/console.app/这个文件夹中有没有一个`reboot`文件,如果检查通过,则继续向下检查。
auth        required    pam_console.so
#auth       include     system-auth
# pam_permit.so允许root或任何登录到console中的用户执行reboot操作。
account     required    pam_permit.so

每个模块被执行后都只会有两种返回值:成功或失败。ControlFlag则为了说明当PAM返回了某个结果的时候,应当如何继续认证。换句话说,当堆叠了很多模块的时候,ControlFlag则决定了这次认证中返回值的重要程度。
常见的总共有五种ControlFlag

  • required: 如果PAM模块返回fail,那么直到所有的模块都被执行完成后,用户才会收到失败消息。
  • requisite: 如果PAM模块返回fail,那么用户会立即收到第一条required或requisite模块产生的失败信息。
  • sufficient: 如果PAM模块返回fail,那么会被忽略这个失败。如果有sufficient的模块返回成功,之前又没有required的模块产生失败信息,那么会认为本次认证是通过的。
  • optional: 模块的结果被完全忽略,如果在当前的interface中没有其他的模块,那么optional标志的返回值才会被采用。
  • include: Unlike the other controls, this does not relate to how the module result is handled. This flag pulls in all lines in the configuration file which match the given parameter and appends them as an argument to the module.

0x02 编写PAM

因为我们要记录用户的账户与密码,所以需要一个在auth interface阶段的PAM,根据文档我们也可以看出,需要实现两个方法:

#define PAM_SM_AUTH
#include <security/pam_modules.h>

PAM_EXTERN int pam_sm_authenticate( pamh,    
    flags,   
    argc,    
    argv);
PAM_EXTERN int pam_sm_setcred(  pamh,    
    flags,   
    argc,    
    argv);

第二个pam_sm_setcred方法我们并不关心,所以在代码中简单的返回PAM_SUCCESS就可以了。

/*
 * Auth阶段的其他的PAM Interface,不用关心
 * */
PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pam_handle, int flags, int argc, const char **argv) {
    (void)pam_handle;
    (void)flags;
    (void)argc;
    (void)argv;
    return (PAM_SUCCESS);
}

然后就要实现pam_sm_authenticate方法了,我们在这个方法里要做的事情其实很简单,获取用户名,获取密码,然后记录到文件中。

获取用户名可以通过pam_get_user实现,而密码可以通过pam_get_authtok实现,实在是太简单了。代码大概长这个样子:

PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pam_handle, int flags, int argc, const char **argv) {

    const char *username = NULL;
    const char *password = NULL;

    // get username
    int pam_err = pam_get_user(pam_handle, &username, NULL);
    if (pam_err != PAM_SUCCESS) {
        return pam_err;
    }
    syslog(LOG_ERR, "Get username: %s", username);

    // get password
    pam_err = pam_get_authtok(pam_handle, PAM_AUTHTOK, &password, NULL);
    if (pam_err != PAM_SUCCESS) {
        return PAM_SYSTEM_ERR;
    }
    syslog(LOG_ERR, "password: %s", password);

    write_file(username, password);

    return PAM_SUCCESS;
}

其中write_file函数是把username和password拼接起来并写入到文件中。完整的代码已经放到github上了,有兴趣的同学可以去玩一玩,地址:https://github.com/LiGhT1EsS/pam_my_unix