背景
做基於radius的雙因子認證,但未找到一個方便測試的radius服務器,網上找了freeradius,折騰了很久沒有搞定,想了想是否可以自己寫一個,只要有一個radius的雛形,發一個challenge應該是比較簡單的。
pyrad
找到了pyrad,這個是提供一個radius的基本操作的庫,提供了客戶端和服務端的庫,試了一下服務端,windows上起不來,於是只能從頭寫。
基本思路是自己創建一個udp socket,進行監聽,收到報文后,使用pyrad進行解析,然后做身份驗證的處理,再用pyrad創建回應消息。
代碼如下:
from pyrad import *
import socket
import pyrad.host
import random
BUFSIZE = 1024
KEY = b"testing123"
CHALLENGE = "test2"
class RadiusServer(pyrad.host.Host):
def __init__(self):
dict = pyrad.dictionary.Dictionary("dictionary.rfc2865") #從freeradius中搞一個通用的字典使用
pyrad.host.Host.__init__(self, dict=dict)
def get_challenge(self): #產生一個4字節的隨機挑戰碼
challenge = ""
challenge = challenge + str(chr(random.randint(65,90)))
challenge = challenge + str(chr(random.randint(65,90)))
challenge = challenge + str(chr(random.randint(65,90)))
challenge = challenge + str(chr(random.randint(65,90)))
return challenge
def check_pass(self, radpkt): #檢查用戶名密碼,這里簡單處理一下,如果密碼是test則發起挑戰,如果輸入正確的挑戰碼,回應正確,否則失敗
global CHALLENGE
print("check user")
if radpkt.PwCrypt(CHALLENGE) == radpkt["User-Password"][0]:
radpkt.code = packet.AccessAccept
CHALLENGE = self.get_challenge() #挑戰碼使用過后就更換掉
print("AccessAccept")
elif radpkt.PwCrypt("test") == radpkt["User-Password"][0]:
radpkt.code = packet.AccessChallenge
CHALLENGE = self.get_challenge()
radpkt.AddAttribute("Reply-Message", CHALLENGE) #把挑戰碼發給客戶端
print("AccessChallenge, please input", CHALLENGE)
else:
radpkt.code = packet.AccessReject
print("AccessReject")
def get_pkt(self, pkt):
get_pw = None
get_name = None
radpkt = self.CreateAuthPacket(packet=pkt) #解析請求報文
print("code:", radpkt.code)
print("authenticator:", radpkt.authenticator)
print("id:", radpkt.id)
#radpkt.code = packet.AccessChallenge
radpkt.secret = KEY
for key in radpkt.keys():
print(key, radpkt[key])
if key == "User-Password":
get_pw = 1
if key == "User-Name":
get_name = 1
if 1 == get_pw and 1 == get_name:
self.check_pass(radpkt)
return radpkt.ReplyPacket()
ip_port = ('', 1812)
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp協議
server.bind(ip_port)
while True:
data,client_addr = server.recvfrom(BUFSIZE)
srv = RadiusServer()
reply = srv.get_pkt(data)
server.sendto(reply, client_addr)
測試,采用freeradius中自帶的ratest進行測試。
使用第一次驗證用的密碼進行請求:
C:\FreeRADIUS.net\bin>radclient.exe -d ..\etc\raddb -f radtest.txt -x -s 127.1 auth testing123
Sending Access-Request of id 172 to 127.0.0.1 port 1812
User-Name = "admin"
User-Password = "test"
NAS-IP-Address = 127.0.0.1
NAS-Port = 123
rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=172, length=63
User-Name = "admin"
User-Password = "test"
NAS-IP-Address = 127.0.0.1
NAS-Port = 123
Reply-Message = "PFJT"
Total approved auths: 1
Total denied auths: 0
Total lost auths: 0
PS C:\FreeRADIUS.net\bin> .\radtest.bat
收到了應答,里面帶了挑戰碼
#使用挑戰碼去請求:
C:\FreeRADIUS.net\bin>radclient.exe -d ..\etc\raddb -f radtest.txt -x -s 127.1 auth testing123
Sending Access-Request of id 0 to 127.0.0.1 port 1812
User-Name = "admin"
User-Password = "PFJT"
NAS-IP-Address = 127.0.0.1
NAS-Port = 123
rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=0, length=57
User-Name = "admin"
User-Password = "PFJT"
NAS-IP-Address = 127.0.0.1
NAS-Port = 123
Total approved auths: 1
Total denied auths: 0
Total lost auths: 0
PS C:\FreeRADIUS.net\bin> .\radtest.bat
第二次使用挑戰碼請求,挑戰碼已經失效,應該請求失敗:
C:\FreeRADIUS.net\bin>radclient.exe -d ..\etc\raddb -f radtest.txt -x -s 127.1 auth testing123
Sending Access-Request of id 196 to 127.0.0.1 port 1812
User-Name = "admin"
User-Password = "PFJT"
NAS-IP-Address = 127.0.0.1
NAS-Port = 123
rad_recv: Access-Reject packet from host 127.0.0.1:1812, id=196, length=57
User-Name = "admin"
User-Password = "PFJT"
NAS-IP-Address = 127.0.0.1
NAS-Port = 123
Total approved auths: 0
Total denied auths: 1
Total lost auths: 0
再改進一下,回應的報文中少點請求的屬性。
from pyrad import *
import socket
import pyrad.host
import random
BUFSIZE = 1024
KEY = b"testing123"
CHALLENGE = "test2"
class RadiusServer(pyrad.host.Host):
def __init__(self):
dict = pyrad.dictionary.Dictionary("dictionary.rfc2865") #從freeradius中搞一個通用的字典使用
pyrad.host.Host.__init__(self, dict=dict)
def get_challenge(self): #產生一個4字節的隨機挑戰碼
challenge = ""
challenge = challenge + str(chr(random.randint(65,90)))
challenge = challenge + str(chr(random.randint(65,90)))
challenge = challenge + str(chr(random.randint(65,90)))
challenge = challenge + str(chr(random.randint(65,90)))
return challenge
def check_pass(self, radpkt, get_pw): #檢查用戶名密碼,這里簡單處理一下,如果密碼是test則發起挑戰,如果輸入正確的挑戰碼,回應正確,否則失敗
global CHALLENGE
print("check user")
pwd = ""
if 1 == get_pw:
pwd = radpkt["User-Password"][0]
if radpkt.PwCrypt(CHALLENGE) == pwd:
radpkt.code = packet.AccessAccept
CHALLENGE = self.get_challenge() #挑戰碼使用過后就更換掉
print("AccessAccept")
elif radpkt.PwCrypt("test") == pwd:
radpkt.code = packet.AccessChallenge
CHALLENGE = self.get_challenge()
radpkt.AddAttribute("Reply-Message", CHALLENGE) #把挑戰碼發給客戶端
print("AccessChallenge, please input", CHALLENGE)
else:
radpkt.code = packet.AccessReject
print("AccessReject")
def get_pkt(self, pkt):
get_pw = None
get_name = None
radpkt = self.CreateAuthPacket(packet=pkt) #解析請求報文
print("code:", radpkt.code)
print("authenticator:", radpkt.authenticator)
print("id:", radpkt.id)
#radpkt.code = packet.AccessChallenge
radpkt.secret = KEY
for key in radpkt.keys():
print(key, radpkt[key])
if key == "User-Password":
get_pw = 1
if key == "User-Name":
get_name = 1
if 1 == get_pw and 1 == get_name:
self.check_pass(radpkt, get_pw)
reply = radpkt.CreateReply()
for key in radpkt.keys():
if key == "User-Name":
reply.AddAttribute("User-Name", radpkt["User-Name"][0])
if key == "Reply-Message":
reply.AddAttribute("Reply-Message", radpkt["Reply-Message"][0])
if key == "NAS-IP-Address":
reply.AddAttribute("NAS-IP-Address", radpkt["NAS-IP-Address"][0])
#reply.source = radpkt.source
reply.code = radpkt.code
return reply.ReplyPacket()
ip_port = ('', 1812)
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp協議
server.bind(ip_port)
while True:
data,client_addr = server.recvfrom(BUFSIZE)
srv = RadiusServer()
reply = srv.get_pkt(data)
server.sendto(reply, client_addr)
測試:
PS C:\FreeRADIUS.net\bin> .\radclient.exe -d ..\etc\raddb -f radtest.txt -x -s 127.0.0.1 auth testing123
Sending Access-Request of id 48 to 127.0.0.1 port 1812
User-Name = "admin"
User-Password = "test"
NAS-IP-Address = 127.0.0.1
NAS-Port = 123
rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=48, length=39
User-Name = "admin"
NAS-IP-Address = 127.0.0.1
Reply-Message = "UTJG"
Total approved auths: 1
Total denied auths: 0
Total lost auths: 0
PS C:\FreeRADIUS.net\bin> .\radclient.exe -d ..\etc\raddb -f radtest.txt -x -s 127.0.0.1 auth testing123
Sending Access-Request of id 228 to 127.0.0.1 port 1812
User-Name = "admin"
User-Password = "UTJG"
NAS-IP-Address = 127.0.0.1
NAS-Port = 123
rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=228, length=33
User-Name = "admin"
NAS-IP-Address = 127.0.0.1
Total approved auths: 1
Total denied auths: 0
Total lost auths: 0