0

3731

nginx登陆认证的几种方案

netxfly   发表于   2015 年 05 月 09 日

nginx的登陆认证的几种方案 ======== # 背景 最近2天在鼓捣Nginx的radius认证,首次尝试了nginx + pam + radius的方式,利用nginx的[ngx_http_auth_pam_module](https://github.com/stogh/ngx_http_auth_pam_module/)模块,以及freeradius的最后更新日期为09年的[pam_radius_auth](http://freeradius.org/pam_radius_auth/)模块搭建好后,radius服务器收到一次性口令后提示认证成功了,但nginx一直循环让我输入密码认证,排查了一天原因,最后以失败而告终。 # 需求 1. 支持现在的一次性口令认证 1. 支持口令过期 由此看来现有的开源方案还真不满足需求,作为一个安静的码男子,现成的轮子不好用就自己动手造一个,于是就有了本篇文章。 # 方案1 1. 提供一个radius认证接口,用户输入一次性口令后再生成一个复杂的token及超时时间(方便客户端app续期用) 1. 将用户名及token实时写入nginx的HTTP basic auth文件中,同时在mongodb存一份 1. 另起一个后台守护进程每隔几秒检查下是否token是否超时,如果超时,则将用户从HTTP basic auth文件中删除 ## radius认证接口的实现 1. 在nginx中单独开一个不受http base auth管制的location,起名为/api,然后反待到后端的radius认证api web app中 ### radius认证api web app的实现 开发语言为python,web框架为Tornado,启用crsf防御功能,代码及效果如下: ![](/static/upload/201505090731345.png) ![](/static/upload/201505090731312.png) 验证部分的代码如下: ``` # Auth class # ---------------------------------------------------------------------------- class Auth(tornado.web.RequestHandler): def initialize(self): self.radius_auth = None self.client = pymongo.MongoClient(CONST_DB.get('host'), CONST_DB.get('port')) self.client.security_detect.authenticate( CONST_DB.get('username'), CONST_DB.get('password'), source='radius' ) db = self.client["radius"] self.collection = db['users'] def get(self): self.render("auth.html") def post(self): self.radius_auth = RadiusAuth(CONST_RADIUS) username = self.get_argument("username", "") or "" password = self.get_argument("password", "") or "" now = datetime.datetime.now() # print username.encode('utf-8'), password.encode('utf-8'), type(username.encode('utf-8')), type(password) ret_auth = self.radius_auth.auth(username.encode('utf-8'), password.encode('utf-8')) # print ret_auth if ret_auth: gen_secret = GenSecuret(username, password) secret = gen_secret.get_secret() values = dict( username=username, password=secret, time=now, status=0 ) self.collection.insert(values) htpasswd = Htpasswd() htpasswd.new_user(username, secret) self.collection.update({'username': username, 'password': secret}, {"$set": {"status": 1}}) ret = dict( username=username, password=secret, status=True ) self.write(json.dumps(ret)) else: ret = dict( username=username, password="", status=False ) self.write(json.dumps(ret)) ``` RadiusAuth是包装好的一个radius认证类,代码如下: ``` from config import CONST_RADIUS, CONST_KEY, CONST_PASSFILE from radius import RADIUS # radius auth class class RadiusAuth(object): def __init__(self, radius_info): self.radius_info = radius_info self.radius = RADIUS( self.radius_info.get('secret'), self.radius_info.get('host'), self.radius_info.get('port'), ) self.radius.timeout = self.radius_info.get('timeout') def auth(self, username, password): if self.radius.authenticate(username, password): return True else: return False ``` Htpasswd是调用htpasswd对http base auth文件操作的类,有新建用户及删除用户2个接口 ``` # htpasswd class class Htpasswd(object): def __init__(self): self.htpasswd = '/usr/bin/htpasswd' self.passwordfile = CONST_PASSFILE def new_user(self, username, password): cmd = "%s -b %s %s %s" % (self.htpasswd, self.passwordfile, username, password) ret = subprocess.call(cmd, shell=True) def del_user(self, username): cmd = "%s -D %s %s" % (self.htpasswd, self.passwordfile, username) ret = subprocess.call(cmd, shell=True) ``` GenSecuret是生成token的算法,就不show了。 ### token过期检查后台程序的实现 每5秒检查一次,如果已过期,则删除用户并将数据库中的状态设为已过期 ``` import time import bson import datetime import pymongo from helper import Htpasswd from config import CONST_DB, CONST_TIMEOUT class Schedule(object): def __init__(self): self.client = pymongo.MongoClient(CONST_DB.get('host'), CONST_DB.get('port')) self.client.security_detect.authenticate( CONST_DB.get('username'), CONST_DB.get('password'), source='radius' ) db = self.client["radius"] self.collection = db['users'] def check(self): ret = self.collection.find({'status': 1}) now = datetime.datetime.now() for item in ret: _id = item.get('_id') create_time = item.get('time') username = item.get('username') login_time = (now - create_time).seconds print login_time if login_time > CONST_TIMEOUT * 60: htpasswd = Htpasswd() htpasswd.del_user(username) self.collection.update({'_id': bson.ObjectId(_id)}, {"$set": {"status": 2}}) # main function # ------------------------------------------------------------------------------------------- if __name__ == '__main__': while True: schedule = Schedule() schedule.check() time.sleep(5) ``` ## 方案1的缺点 1. 频繁读写http auth文件,用户并发数上较大的话,容易出问题 1. 扩容时还要考虑对多台服务器中的http auth文件进行同步,虽然说可使用分布式文件系统或每台再暴露出一个接口专门处理http auth实时同步的情况,但是架构复杂了,出错的可能性较大 # 方案2 不使用nginx的http base auth模块,直接用nginx的lua模块实现一个,用户认证时直接从数据库中取数据,这就就可避免方案1中遇到的2个问题了。 由于是新上线业务,直接用openresty了,里面集成了好多lua的模块,省去了自己编译nginx第3方模块的麻烦。 ## openresty的配置 1. 为openresty安装mongodb驱动 github地址为:https://github.com/bigplum/lua-resty-mongol 1. 在openresty的配置文件中指定lua脚本的位置 ``` http { include mime.types; default_type application/octet-stream; lua_package_path "/usr/local/openresty/nginx/conf?.lua;;"; ``` 1. access_by_lua_file指令指定access阶段执行的认证用的lua脚本的位置 Nginx 的请求处理阶段共有 11 个之多,3个比较常见的请求执行阶段的先后顺序依次是 rewrite 阶段、access 阶段以及 content 阶段 ``` location /auth { access_by_lua_file /usr/local/openresty/nginx/conf/auth.lua; } ``` ### lua认证的实现 ``` local p = "/usr/local/openresty/lualib" local m_package_path = package.path package.path = string.format("%s?.lua;%s?/init.lua;%s", p, p, m_package_path) -- http base auth function auth() local username = ngx.var.remote_user local password = ngx.var.remote_passwd if auth_monogodb(username, password) then return end ngx.header.www_authenticate = [[Basic realm="sinasec auth"]] ngx.exit(401) end -- auth by mongodb function auth_monogodb(username, password) local mongo = require "resty.mongol" local host_info = { host="10.1.1.1", port=27017, username="myuser", password="mypass" } conn = mongo:new() conn:set_timeout(1000) ok, err = conn:connect(host_info["host"], host_info["port"]) if not ok then ngx.log("connect failed: "..err) end local db = conn:new_db_handle("radius") ok, err = db:auth(host_info["username"], host_info["password"]) -- ngx.say(ok, err) col = db:get_col("users") r = col:find_one({username=username, password=password, status=1}) if r == nil then return false else ngx.say(r["password"]) return true end end -- call auth function auth() ``` ## 方案2的缺点 1. 同方案1一样,需要后台跑一个进程判断用户tokan的超时,并设置状态 # 方案3 将mongodb换成redis,利用redis key的超时机制,将token超时的处理交给redis 明天贴出demo代码

标签:nginx认证 继续阅读

0

1662

redis和mongodb 空口令扫描器

netxfly   发表于   2015 年 04 月 13 日

# 用法 1. iplist文件中以ip:port的方式添加需要扫描的服务器 2. github地址: - scan_redis, https://github.com/netxfly/crack_ssh/blob/master/scan_redis.go - scan_mongodb, https://github.com/netxfly/crack_ssh/blob/master/scan_mongodb.go 3. 效果如下: ![](/static/upload/201504130933034.png) ![](/static/upload/201504130933112.png)

标签:redis_mongodb 空口令扫描 继续阅读

2

1611

go语言版ssh口令破解工具

netxfly   发表于   2015 年 04 月 05 日

## 使用说明: ![](/static/upload/201504050545178.jpg) 1. iplist的格式为ip:port,如111.111.111.111:22 2. user.txt为用户名字典 3. password.txt为密码字典 4. github:https://github.com/netxfly/crack_ssh/blob/master/scan_ssh.go ## 源码: ``` package main import ( "bufio" "bytes" "fmt" "github.com/btcsuite/golangcrypto/ssh" "log" "os" "runtime" "strings" "time" ) type HostInfo struct { host string port string user string pass string is_weak bool } // help function func Usage(cmd string) { fmt.Println(strings.Repeat("-", 50)) fmt.Println("SSH Scanner by hartnett [x@xsec.io]") fmt.Println("Usage:") fmt.Printf("%s iplist userdic passdic\n", cmd) fmt.Println(strings.Repeat("-", 50)) } // read lime from file and Scan func Prepare(iplist, user_dict, pass_dict string) (slice_iplist, slice_user, slice_pass []string) { iplistFile, _ := os.Open(iplist) defer iplistFile.Close() scanner := bufio.NewScanner(iplistFile) scanner.Split(bufio.ScanLines) for scanner.Scan() { slice_iplist = append(slice_iplist, scanner.Text()) } user_dictFile, _ := os.Open(user_dict) defer user_dictFile.Close() scanner_u := bufio.NewScanner(user_dictFile) scanner_u.Split(bufio.ScanLines) for scanner_u.Scan() { slice_user = append(slice_user, scanner_u.Text()) } pass_dictFile, _ := os.Open(pass_dict) defer pass_dictFile.Close() scanner_p := bufio.NewScanner(pass_dictFile) scanner_p.Split(bufio.ScanLines) for scanner_p.Scan() { slice_pass = append(slice_pass, scanner_p.Text()) } return slice_iplist, slice_user, slice_pass } // Scan function func Scan(slice_iplist, slice_user, slice_pass []string) { for _, host_port := range slice_iplist { fmt.Printf("Try to crack %s\n", host_port) t := strings.Split(host_port, ":") host := t[0] port := t[1] n := len(slice_user) * len(slice_pass) chan_scan_result := make(chan HostInfo, n) for _, user := range slice_user { for _, passwd := range slice_pass { host_info := HostInfo{} host_info.host = host host_info.port = port host_info.user = user host_info.pass = passwd host_info.is_weak = false go Crack(host_info, chan_scan_result) for runtime.NumGoroutine() > runtime.NumCPU()*300 { time.Sleep(10 * time.Microsecond) } } } done := make(chan bool, n) go func() { for i := 0; i < cap(chan_scan_result); i++ { select { case r := <-chan_scan_result: // fmt.Printf("Try %s:%s, user: %s, password: %s\n", r.host, r.port, r.user, r.pass) if r.is_weak { var buf bytes.Buffer logger := log.New(&buf, "logger: ", log.Ldate) logger.Printf("%s:%s, user: %s, password: %s\n", r.host, r.port, r.user, r.pass) fmt.Print(&buf) } case <-time.After(1 * time.Second): // fmt.Println("timeout") break } done <- true } }() for i := 0; i < cap(done); i++ { // fmt.Println(<-done) <-done } } } // crack passwd func Crack(host_info HostInfo, chan_scan_result chan HostInfo) { host := host_info.host port := host_info.port user := host_info.user passwd := host_info.pass is_ok := host_info.is_weak config := &ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{ ssh.Password(passwd), }, } client, err := ssh.Dial("tcp", host+":"+port, config) if err != nil { is_ok = false // panic("Failed to dial: " + err.Error()) } else { session, err := client.NewSession() defer session.Close() if err != nil { is_ok = false } else { is_ok = true } } host_info.is_weak = is_ok chan_scan_result <- host_info } // main function func main() { runtime.GOMAXPROCS(runtime.NumCPU()) if len(os.Args) != 4 { Usage(os.Args[0]) } else { Usage(os.Args[0]) iplist := os.Args[1] user_dict := os.Args[2] pass_dict := os.Args[3] Scan(Prepare(iplist, user_dict, pass_dict)) } } ```

标签:golang ssh scan 继续阅读

4

1284

2015年1-2月份书单

netxfly   发表于   2015 年 02 月 13 日

2015年1月份 === 1. Erlang程序设计(第2版) 2. Boost程序库完全开发指南 3. Swift语言实战晋级 2015年2月份 === 1. JavaScript面向对象编程指南 1. Python编程(第4版上下册)

标签:每月书单 继续阅读

0

1232

2014年12月份书单

netxfly   发表于   2015 年 01 月 07 日

已经15年1月份了,把去年12月份的书单补上。 - - - - 《The docker book中文版》,第一本docker的书,目前市面上有2本,另外一本是国内的开源书,地址:http://yeasy.gitbooks.io/docker_practice/content/ - 《Cocos2dx实战: C++卷》 - 《Flask WEB开发》 - 《Python网络编程攻略》 - 《Objective-C编程全解(第3版)》,日本OC书籍中的圣经,以前看过Javascript编程全解,应该错不了 - 《Objective-C 2.0 Mac和iOS开发实践指南(原书第2版)》,又一本OC的书,一般 - C#图解教程(第4版) - TCP Sockets编程 - - - 有好多东西想看,想学,最后发现只是学会了计划中的一小部分。 新的一年,继续坚持学习,方向如下: - 信息安全,曾经投入过多年时间的老本行,但要与时俱进地更新知识 - 开发:移动、游戏,web和后端方向 - 语言:c++、python、ruby、golang,oc,swift、js、lua、clojure,groovy - 框架:cocos2d-x,ror,sinatra,django,tornado,flask,beego,martini,cocoa touch

标签:每月书单 继续阅读

较旧的文章 较新的文章