Linux下使用pam_python实现SSH的双因子认证登录.md
关键字
Linux PAM Python SSH 2 Two Multi Factor Authentication Login 双因子 多因子 密保 TOKEN 一次性口令 PASSPOD OTP yubikey 认证 安全 登录
引言
Linux系统管理员(System Administrator,SA)经常碰到的问题就是放在公网的服务器经常被人猜测密码,每天都可以从系统日志里看到探测密码的信息,再加上最近很多厂商泄露了包含用户密码的数据库,撞库的行为也逐步开始转移到SSH上。
最初SA的防御手段一般是限制IP地址、修改SSH端口、部署失败一定次数就锁定或者封IP的程序或者脚本,更有极客想出了敲门3次端口才开放的办法,可谓无所不用其极。但是这些办法很多都不是很方便,改了端口,连接时需要指定端口;限制了IP地址,发现在家上网就登录不了了,封锁脚本可能把自己也锁定了。
在大公司里一般是采用的“RSA SecurID”方案,或者类似的技术。我们称其为双因子认证或者多因子认证(Two Factor Authentication;MFA,Multi Factor Authentication),在输入密码的同时需要输入一个一次性口令(OTP,One Time Password)。这种方案也有软件实现和硬件实现,软件例如google authenticator、Symantec Validation and ID Protection (VIP) ;硬件例如 RSA SecurID、飞天诚信的密保产品。
使用RSA SecurID的方案看起来虽然很好,但是他需要独立部署RSA Server,需要占用一台服务器,并且Server端软件是收费的,RSA SecurID密保也是收费的。
有没有免费的办法?
有啊,今天就来介绍一个。
实现方法
最简单的实现的方式,用户登录时需要输入用户名+PIN+密码方式才能登录。
这里的PIN是一个字符串,例如”ipcpu.com”,固定死的,不会变。
[root@IPCPU-0 security]# ssh root@192.168.110.11
Enter Your PIN:
Password:
Last login: Mon Mar 21 00:44:26 2016 from 192.168.110.11
[root@IPCPU-11 ~]#
安装pam_python模块
pam_python (注意不是python_pam)是一款开源的软件,将需要使用C语言编写的PAM模块转换成了可以使用python语言来写,顿时感觉方便多了。
官网地址:http://pam-python.sourceforge.net/
github备份:https://github.com/ipcpu/pam-python-ipcpu (修正了CentOS的报错,放了一些案例和中文说明进去)
安装方法比较简单
##@@安装编译依赖
yum install pam pam-devel -y
##@@解压进入src目录
make lib
##@@拷贝.so文件到/lib64/security/
cp build/lib.linux-x86_64-2.6/pam_python.so /lib64/security/
编写Python程序实现认证流程
我们进入到 /lib64/security/ 编写一个auth.py文件,内容如下
#!/usr/bin/env python
# -*- coding=utf-8 -*-
"""
#这个函数是本次的重点内容哦,判断用户输入的PIN是否为ipcpu.com
"""
def pam_sm_authenticate(pamh, flags, argv):
for attempt in range(0,3):
msg = pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, "Enter Your PIN: ")
resp = pamh.conversation(msg)
if resp.resp == "ipcpu.com":
return pamh.PAM_SUCCESS
else:
continue
return pamh.PAM_AUTH_ERR
"""
#以下都是默认函数
"""
def pam_sm_setcred(pamh, flags, argv):
return pamh.PAM_SUCCESS
def pam_sm_acct_mgmt(pamh, flags, argv):
return pamh.PAM_SUCCESS
def pam_sm_open_session(pamh, flags, argv):
return pamh.PAM_SUCCESS
def pam_sm_close_session(pamh, flags, argv):
return pamh.PAM_SUCCESS
def pam_sm_chauthtok(pamh, flags, argv):
return pamh.PAM_SUCCESS
配置SSHD,开启PAM模块
修改/etc/pam.d/sshd,新增一行,如下
#%PAM-1.0
auth requisite pam_python.so auth.py
auth required pam_sepermit.so
auth include password-auth
修改/etc/ssh/sshd_config,打开ChallengeResponse
ChallengeResponseAuthentication yes
重启SSHD服务,接下来就可以测试了。
如果出现错误,日志会写到/var/log/secure里面。
进阶-独立的PIN
使用固定的PIN优点太low了,接下来我们介绍进阶的办法,每个人用自己的PIN。
首先PIN需要有个地方存放起来,我们就直接使用/etc/passwd的comment字段来存储。
可以通过命令 usermod来修改。如下,
[root@IPCPU 2factor-with-PIN]# usermod -c ',,15801581158,' ipcpu
[root@IPCPU 2factor-with-PIN]# cat /etc/passwd |grep ipcpu
ipcpu:x:501:501:,,15801581158,:/home/ipcpu:/bin/bash
[root@IPCPU 2factor-with-PIN]#
python的代码也需要修改下,如下
import random, string, hashlib, requests
import pwd, syslog
def auth_log(msg):
syslog.syslog("IPCPU-PAM-AUTH: " + msg)
def get_user_number(user):
"""Extract user's phone number for pw entry"""
try:
comments = pwd.getpwnam(user).pw_gecos
except KeyError: # Bad user name
auth_log("No local user (%s) found." % user)
return -1
try:
return comments.split(',')[2] # Return Office Phone
except IndexError: # Bad comment section format
auth_log("Invalid comment block for user %s. Phone number must be listed as Office Phone" % (user))
return -1
def pam_sm_authenticate(pamh, flags, argv):
try:
user = pamh.get_user()
user_number = get_user_number(user)
except pamh.exception, e:
return e.pam_result
if user is None or user_number == -1:
msg = pamh.Message(pamh.PAM_ERROR_MSG, "Unable to send one time PIN.\nPlease contact your System Administrator")
pamh.conversation(msg)
return pamh.PAM_AUTH_ERR
for attempt in range(0,3): # 3 attempts to enter the one time PIN
msg = pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, "Enter Your PIN: ")
resp = pamh.conversation(msg)
if resp.resp == user_number:
auth_log("user: " + user + " login successful with PIN.")
return pamh.PAM_SUCCESS
else:
auth_log("user: " + user + " login failed with PIN.")
continue
return pamh.PAM_AUTH_ERR
def pam_sm_setcred(pamh, flags, argv):
return pamh.PAM_SUCCESS
def pam_sm_acct_mgmt(pamh, flags, argv):
return pamh.PAM_SUCCESS
def pam_sm_open_session(pamh, flags, argv):
return pamh.PAM_SUCCESS
def pam_sm_close_session(pamh, flags, argv):
return pamh.PAM_SUCCESS
def pam_sm_chauthtok(pamh, flags, argv):
return pamh.PAM_SUCCESS
继续进阶-短信
上一步,我们使用了每个用户独立的PIN来进行双因子认证,如果我们把PIN换成自己的手机号,然后在登陆的时候先生成随机字符串,然后短信发送到用户的手机上,对比字符串是否一致,这样我们就实现了基于短信形式的双因子认证。
这部分代码就留给读者自行练习了。需要注意的是为了防止别人猜测密码时收到大量短信,这里最好连手机号也对比认证下。
参考资料
谷歌google authenticator算法分析
https://garbagecollected.org/2014/09/14/how-google-authenticator-works/
RSA SecurID相关资料
http://www.slideshare.net/Sandra4211/rsa-security-authentication-ace-serversecurid
鸟哥关于PAM的讲述
http://vbird.dic.ksu.edu.tw/linux_basic/0410accountmanager_5.php