Theory/Cipher protocol

kerberos 버전별 구현[소스]

D4tai1 2019. 6. 30.

오늘부터는 글의 자유도를 높이기 위해 의식의 흐름기법에 따라 끄적끄적 하도록 하겠습니다ㅏㅏ~

 

kerberos? 커베로스 커버로스 .. 여러가지 이름으로 불리네요.

 

저는 처음 배울 때 커버로스로 배웠기 때문에 커버로스라고 말을 하겠습니다.

 

커버로스는 version1부터 version5까지 나왔는데

 

여기서는 버전 1과 버전 2를 직접 구현하러 떠나봅시다~

 


 

1. 이 커버로스에 대해 알아봅시다!!

 

1-1) 커버로스란 무엇일까요?

▶ 검색을 해보니 그리스신화에서 저승문을 지키는 머리가 3개인 개님의 이름? 이라고 나오네요.

 

▶ 조금더 자세히 설명하면 MIT에서 개발된 대칭키 방식에 의한 인증시스템 정도로 볼 수 있겠네요.

 


 

2. 커버로스 버전1

 

2-1) 커버로스 버전1의 원리를 먼저 알아볼까요?

[그림1] 커버로스 버전1의 원리

# 그림이 조금 그렇지만... 에헴.. 쉽게 이해해보도록 합니다!!

 

[1] 놀이동산 손님이 나야 나 알지? 놀이동산에 들어갈 수 있게 티켓 내놔아!! 라고 징징댑니다.

[2] 놀이동산 알바생이 티켓 줄게~ 대신 딴사람 주기만해봐!! 라고 무언의 압박을 합니다.

[3] 손님은 받은 티켓을 놀이동산에 입장할 때 티켓있는 정당한 사용자라고 당당하게^^ 보여줍니다. 

[4] 티켓확인하는 알바생은 이 티켓이 암표가 아닌지 확인합니다!! 

 

이제 아래 번호별로 매칭해서 보도록 하지요~

 

[1] 클라이언트 -> 인증서버(Authentication Server)

 ▶ 클라이언트 => 자신의 ID, 자신의 패스워드, 접속하려는 서버의 ID를 인증서버로 전송을 합니다.

 

[2] 인증서버 -> 클라이언트

 ▶ 인증서버 =>  master_keyEncryption[클라이언트의 ID, 클라이언트의 IP, 접속하려는 서버의 ID] = 티켓

 

[3] 클라이언트 -> 인증서버

 ▶ 클라이언트 => 클라이언트ID와 티켓을 접속할 서버에게 전송합니다.

 

[4] 접속할 서버 -> 클라이언트 

 ▶ 티켓을 복호화해서 베리파이(verify) 한 이후

클라이언트의 ID를 비교하고,

현재 접속한 IP와 티켓에 적힌 IP주소를 비교하고,

내 서버번호를 확인하고 서비스를 제공합니다.

 

나름 까다로운 과정이 들어가 있네요~

 

역시 보안은 안 할 수도 없고 하자니 번거로운...

불편한 진실이라는 점을 알 수 있네요^^

 

조금 불편해도 안전해야

마음도 편하고 든든하죠 하하!!

 

 


이 부분을 돌려봅시다!! python3으로

 

2-2) 커버로스 버전1[소스]

# mylib.py는 triple des로 암호화하기위해 라이브러리를 추가하였습니다.

# 이 부분은 아래 링크에서 나름 열심히? 설명이 되어 있으므로 넘어갈게요.

# 참고자료 : https://ccurity.tistory.com/249

 

[1] mylib.py

from Crypto.Cipher import DES3
from Crypto.Hash import SHA512

class MyDES3():
	def __init__(self,key,iv='iviv'):
		key = key.encode()		
		iv = iv.encode()
		hash = SHA512.new(key)
		hv = hash.digest()
		self.key = hv[:24]
		hash.update(iv)
		hv = hash.digest()
		self.iv = hv[:8]
		
	def enc(self,msg1): # msg1 is unicode 		
		msg,addlen=self.make8stringEx(msg1)
		msg=self.addh(msg,addlen)
		msg = msg.encode()
		des3 = DES3.new(self.key,DES3.MODE_CBC,self.iv)
		return des3.encrypt(msg)
		
	def dec(self,msg1): # msg1 is byte code
		des3 = DES3.new(self.key,DES3.MODE_CBC,self.iv)
		msg = des3.decrypt(msg1)
		msg = self.cutmsg(msg)
		return msg
		
	def cutmsg(self,msg):
		msg = msg.decode('utf-8')
		index= msg.find('#')
		sub = msg[:index]
		nsize = int(sub)
		nmsg=msg[8:len(msg) - nsize]
		return nmsg
		
	def addh(self,msg,size):
		numstr = str(size)
		fsize = 8 - len(numstr)
		fill = '#'*fsize
		numstr.encode()
		nmsg = '123'
		nmsg = numstr + fill  + msg
		return nmsg
	def make8stringEx(self,msg):		
		filler = b''
		mlen = len(msg)
		if mlen % 8 != 0:
			addlen = 8 - mlen % 8
			filler = '0'*addlen
		msg += filler
		return msg,addlen
		
def test():
	pass
	msg = '1234 password'
	des3 = MyDES3('key')
	cmsg= des3.enc(msg)
	print(msg)
	print(cmsg)
	pmsg = des3.dec(cmsg)
	print(pmsg)
	
#test()

 

# 참고_★빵빵!! = client.py의 ip적는 부분은 통신할 상대의 ip주소로 수정하여야 통신이 됩니다~

 

[2] client.py

import socket
import pickle
import mylib
import base64
import sys

def getTicket(IDc, pw, IDv):	# (ID, PW, 접속할 서버번호)
	host = '192.168.234.102'	# 접속할 티켓서버 IP
	port = 10100				# 접속할 티켓서버의 port
	
	client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	# 클라이언트 소켓 = 전송할 주소체계 타입(IPv4, TCP)	

	client.connect((host, port))	
	# 소켓으로 연결시도((ip, port))

	send_data = [IDc, pw, IDv]
	send_pickle = pickle.dumps(send_data)
	# 리스트를 전송하기위해 시리얼라이즈

	client.send(send_pickle)
	# 티켓서버로 리스트덤프를 전송

	ticket = client.recv(1024)
	# 접속할 서버에 대한 티켓을 받음

	if not ticket:	# 티켓을 정상적으로 받지 못한 경우
		print('Not receive a ticket!!')
		client.close()
		sys.exit(1)

	client.close()	# 티켓서버와의 세션끊기

	return ticket

def server_connect(IDc, ticket):
	host = '192.168.234.102'	# 접속할 서비스서버의 IP
	port = 1111					# 접속할 서비스서버의 port

	client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	client.connect((host, port))

	send_data = [IDc, ticket]
	send_pickle = pickle.dumps(send_data)
	# 클라이언트의 ID와 티켓이 담긴 리스트를 전송하기위해 시리얼라이즈	
	
	client.send(send_pickle)
	# 접속할 서버로 리스트덤프를 전송

	recv_pickle = client.recv(1024)
	# 서버로로부터 결과 받기
	if recv_pickle == b'':
		print('Input Error!!')
		client.close()
		sys.exit(1)

	recv_data = pickle.loads(recv_pickle)
	# 덤프를 디시리얼라이즈

	client.close() 	# 접속서버와의 세션끊기

	return recv_data


def main():
	IDc = input('user_ID : ')	# user100
	pw = input('user_PW : ')	# d4tai1
	IDv = input('server : ') 	# 1
	# 클라이언트의 ID, PW, 접속할서버번호 입력

	ticket = getTicket(IDc, pw, IDv)
	# 클라이언트의 ID, PW, 접속할서버번호로 티켓을 얻는 과정

	recv = server_connect(IDc, ticket)
	# 클라이언트의 ID와 티켓을 들고 서버에 접속하는 과정

	print('recv : {}'.format(recv))
	

if __name__ == '__main__':
	main()

 

[3] AS.py

import socket
import pickle
import mylib
import base64
import sys

def authentication(IDc, pw):
	if IDc == 'user100' and pw == 'd4tai1':
		return True
	else:
		return False

def server():
	host = ''
	port = 10100	# Listening port
	
	key = 'kerveros_1'	# des3 암호화 키
	iv = 'ticket'		# des3 초기벡터
	des3 = mylib.MyDES3(key, iv)	# 암호화 하기 위해 객체 생성
	
	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	# 서버 소켓 = 전송할 주소체계 타입(IPv4, TCP)

	server.bind((host, port))
	# 소켓을 포트에 바인딩(매핑) 


	try:
		while True:
			print('----------------------------------------')
			print(' [*] Ticket Server')
			print(' [*] Listening...')
			server.listen(1)	# 접속이 있을때까지 포트열고 대기

			client, addr = server.accept()	
			# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환

			print(' [+] Connect Client')
			print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
		
			recv_pickle = client.recv(1024) 
			# 클라이언트가 티켓을 얻기 위한 정보(리스트덤프)

			recv_data = pickle.loads(recv_pickle)
			# 리스트덤프를 디시리얼라이즈

			if not authentication(recv_data[0], recv_data[1]):
				client.close()	# 정보가 일치하지 않으면 연결종료
				print('Input information is different!!')

			else:
				recv_data[1] = addr[0]
				# pw는 확인이 끝났으므로 클라이언트 주소를 저장	
	
				print(' [+] client info = {}'.format(recv_data))
			
				ticket = [x for x in recv_data]
				# 티켓 = [클라이언트ID, 클라이언트address, 접속서버번호]

				ticket_pickle = pickle.dumps(ticket)
				# 티켓리스트를 시리얼라이즈
	
				ticket_pickle = ticket_pickle.decode('utf-8', 'ignore')
				# des3.enc()에서 str타입으로 받으므로 타입변환

				ticket_pickle_des3 = des3.enc(ticket_pickle)
				# 트리플DES로 티켓덤프를 암호화	
	
				ticket_pickle_des3_base64enc = base64.b64encode(ticket_pickle_des3)
				# 암호화 티켓을 전송하기위해 base64로 인코딩
		
				client.send(ticket_pickle_des3_base64enc)
				# 인코딩된 티켓을 클라이언트에게 전송		
	
				client.close()	# 클라이언트와 세션종료
	
	except KeyboardInterrupt:
		print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
		# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력		

	finally:
		print('server end')
		# 서버종료 시 출력

if __name__ ==  '__main__':
	server()

 

[4] v.py

import socket
import pickle
import mylib
import base64
import sys
from datetime import datetime

def authentication(IDc, addr, ticket):
	if IDc == ticket[0] and addr == ticket[1] and '1' == ticket[2]:
		# 현재 서버에 접속한 ID와 티켓에 적힌 ID가 동일한지
		# 현재 서버에 접속한 IP주소와 티켓에 적힌 IP주소가 동일한지
		# 현재 서버의 번호와 티켓에 적혀있는 서버의 번호가 동일한지
		return True
	else:
		return False
	

def server():
	host = ''
	port = 1111
	
	key = 'kerveros_1'
	iv = 'ticket'
	des3 = mylib.MyDES3(key, iv)
	
	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	server.bind((host, port))


	try:
		while True:
			print('----------------------------------------')
			print(' [*] Server')
			print(' [*] Listening...')
			server.listen(1)	# 접속이 있을때까지 포트열고 대기

			client, addr = server.accept()
			# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환
	
			print(' [+] New Connect Client')
			print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
		
			recv_pickle = client.recv(1024) 
			# [클라이언트ID, 티켓]의 리스트덤프 받기

			recv_data = pickle.loads(recv_pickle)
			# 받은 리스트덤프를 디시리얼라이즈		

			IDc, ticket_pickle_des3_base64enc = recv_data[0], recv_data[1]	
			# 클라이언트ID와 티켓덤프를 암호화후 인코딩한 정보를 저장

			ticket_pickle_des3 = base64.b64decode(ticket_pickle_des3_base64enc)
			# base64 디코딩 후 저장

			ticket_pickle = des3.dec(ticket_pickle_des3)
			# 트리플 DES3로 복호화 후 저장
	
			ticket_pickle = ticket_pickle.encode()
			# byte타입으로 변환

			ticket = pickle.loads(b'\x80'+ticket_pickle)
			# 티켓덤프를 메모리에 로드

			if not authentication(IDc, addr[0], ticket):
				client.close() 	# 정보가 일치하지 않으면 연결종료
				print('user_ID or address or server_number is different!!')
			else:
				send_data = [datetime.now().year, datetime.now().month, datetime.now().day]
				# 시간정보를 리스트로 저장

				send_data_pickle = pickle.dumps(send_data)
				client.send(send_data_pickle)
				print(send_data)

				client.close()
	
	except KeyboardInterrupt:
		print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
		# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력

	finally:
		print('server end')
		# 서버종료 시 출력

if __name__ ==  '__main__':
	server()

 


 

2-3) 커버로스 버전1[사용방법]

 
[1] [python3 AS.py] 명령어로 AS서버 동작


[2] [python3 v.py] 명령어로 서비스서버 동작


[3] [python3 client.py] 명령어로 클라이언트 동작


[4] 클라이언트에서 총 3개를 입력을 받음
 ▶ user_ID를 (user100)으로 입력
 ▶ user_PW를 (d4tai1)로 입력
 ▶ server를 (1)로 입력


 - user_ID와 user_PW가 일치하지 않을 경우 티켓발급 불가
 - server번호가 다를 경우 티켓을 발급되지만 서버에서 서비스 받을 수 없음
 - 티켓서버에 접속한 IP주소와 서비스서버에 접속한 IP주소가 다를 경우 즉시 서버와 연결종료

 

와 같이 사용할 수 있습니다.

 

 

2-4) 커버로스 버전1[시연]

[그림2] Client_ID가 다른경우

 

 

[그림3] Client_PW가 다른경우 

 

 

[그림4] 접속할 서버의 번호가 다른경우

 

 

[그림5] 모두 정상입력한 경우

 

총 4가지 경우가 있는 것을 확인할 수가 있습니다.

그렇다면 커버로스 버전1은 어떠한 문제점이 있고, 

커버로스 버전2에서는 어떠한 기능이 추가되었을까요?

 

2-5) 커버로스 버전1의 취약점

[1] 서버에 접속할 때마다

비밀번호를 입력해야 한다!!

 

[2] 내 패스워드 스니핑해서 보세요~

non_encryption

 


 

3. 커버로스 버전2에 대해서 알아봅시다.

 

3-1) 커버로스 버전1의 취약점 해결

[1] 티켓을 재사용해서

비밀번호를 계속입력받지 않는

참신한?방법이 있지만,

또 다른 문제가 있다.

 

그것은 바로 !!!!!!

다른 서버에 접속할 경우는

또오오 입력을 해야한다.

 

[2] 패스워드를 공유할 수는 없으므로

티켓발행서버(Ticket_Granting_Server)를 

도입한다!!

 

이러면!! 서버를 또 만들어야한다..는

말로 들리는데...

 

어쨋든 구현해 봅시다~

 


 

3-2) 커버로스 버전2의 프로토콜

# 위에서 어느정도 설명을 했으니

여기서는 이론을 적어보도록 할게요~

 

[1] 클라이언트는 인증서버에게

클라이언트ID와 티켓서버의ID를 던져줍니다.

 

[2] 인증서버는 클라이언트에게

[클라이언트ID, 클라이언트 접속IP, 티켓서버의ID, TimeStamp1, Lifetime1]을 

티켓서버의 키로 암호화 합니다.

# Timestamp1은 티켓 생성시간을 의미하고

# Lifetime1은 티켓 유효기간을 의미해요~

 

이 말은 나중에 이 티켓이 유효한지에

대해 검증한다는 말로 들리죠?ㅎㅎ

 

[3] 클라이언트는 티켓서버에게 

클라이언트ID와 접속할 서버의ID, 티켓(티켓서버를 이용할 수 있는)을

전송합니다.

 

[4] 티켓서버는 클라이언트에게

접속할 서버의 티켓을 발행해줍니다.

 

접속할 서버의 티켓의 내용은?

[클라이언트ID, 클라이언트 접속IP, 접속할 서버의ID, TimeStamp2, Lifetime2]을

접속할 서버의 키로 암호화 한 것을 말합니다.

 

[5] 클라이언트는 접속할 서버에게  

클라이언트ID와 티켓(접속할 서버를 이용할 수 있는)을 전송합니다.

 

이 정도 되었으면 구현하러 떠나봅시다~

 

역시 이론보다는 ^^

 


 

3-3) 커버로스 버전2[소스]

# mylib.py는 커버로스 버전1에서 사용한 것과

동일하므로 소스는 위에꺼 사용해도 됩니다.

 

[1] client.py

import socket
import pickle
import mylib
import base64
import sys

def getTicket_tgs(IDc, IDv):	# (ID, PW, 접속할 서버번호)
	host = '192.168.234.102'	# 접속할 인증서버 IP
	port = 10100				# 접속할 인증서버의 port
	
	conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	# 클라이언트 소켓 = 전송할 주소체계 타입(IPv4, TCP)	

	conn.connect((host, port))	
	# 소켓으로 연결시도((ip, port))

	send_data = [IDc, IDv]
	send_pickle = pickle.dumps(send_data)
	# 리스트를 전송하기위해 시리얼라이즈

	conn.send(send_pickle)
	# 인증서버로 리스트덤프를 전송

	ticket_tgs = conn.recv(1024)
	# 접속할 티켓서버에 대한 티켓을 받음

	if not ticket_tgs:	# 티켓서버의 티켓을 정상적으로 받지 못한 경우
		print('Not receive a ticket_tgs!!')
		conn.close()
		sys.exit(1)

	conn.close()	# 인증서버와의 세션끊기

	return ticket_tgs

def getTicket_server(IDc, IDv, ticket_tgs):
	host = '192.168.234.102'	# 접속할 티켓서버의 IP
	port = 20100					# 접속할 티켓서버의 port

	conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	conn.connect((host, port))
	
	send_data = [IDc, IDv, ticket_tgs]
	# 티켓서버로 전송할 데이터

	send_pickle = pickle.dumps(send_data)
	# 클라이언트의 ID와 티켓이 담긴 리스트를 전송하기위해 시리얼라이즈	
	
	conn.send(send_pickle)
	# 접속할 티켓서버로 리스트덤프를 전송

	ticket_v = conn.recv(1024)
	# 티켓서버로로부터 결과 받기

	if not ticket_v:	# 티켓이 오지 않았다면
		print('Not receive a Ticket_v!!')
		conn.close()
		sys.exit(1)

	conn.close()	# 접속세션과의 세션끊기
	
	return ticket_v

def server_connect(IDc, ticket):
	host = '192.168.234.102'	# 접속할 서비스서버의 IP
	port = 30100				# 접속할 서비스서버의 port

	conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	conn.connect((host, port))

	send_data = [IDc, ticket]
	# 서비스서버로 전송할 데이터

	send_pickle = pickle.dumps(send_data)
	# 클라이언트의 ID와 티켓이 담긴 리스트를 전송하기위해 시리얼라이즈	
	
	conn.send(send_pickle)
	# 접속할 서버로 리스트덤프를 전송

	recv_pickle = conn.recv(1024)
	# 서버로로부터 결과 받기

	if not recv_pickle:		# 서버로부터 데이터를 받지 못했다면
		print('Data not received!!')
		conn.close()
		sys.exit(1)

	recv_data = pickle.loads(recv_pickle)
	# 덤프를 디시리얼라이즈

	conn.close() 	# 접속서버와의 세션끊기

	return recv_data


def main():
	IDc = input('user_ID : ')			# user100
	IDtgs = input('ticket_server : ') 	# 10
	# 클라이언트의 ID, 접속할 티켓서버번호 입력

	IDv = input('service_server : ')	# 100
	# 접속할 서비스서버번호 입력

	ticket_tgs = getTicket_tgs(IDc, IDtgs)
	# 클라이언트의 ID, 접속할 티켓서버번호로 티켓서버티켓을 얻는 과정

	ticket_v = getTicket_server(IDc, IDv, ticket_tgs)
	# 클라이언트의 ID, 접속할 서비스서버번호와 티켓서버티켓으로 서비스서버 티켓을 얻는 과정
	
	recv = server_connect(IDc, ticket_v)
	# 클라이언트의 ID와 서비스서버티켓으로 접속시도

	print('recv : {}'.format(recv))
	

if __name__ == '__main__':
	main()

 

[2] AS_server.py

import socket
import pickle
import mylib
import base64
import sys
import time

def authentication(IDc, IDtgs):
	if IDc == 'user100' and IDtgs == '10':
	# user_ID와 티켓서버의 번호를 확인
		return True
	else:
		return False

def makeTicket(IDc, addr, IDtgs):
	key = 'kerberos_v2'
	iv = 'AS_Server'

	timestamp = [str(x) for x in time.localtime()[:6]]

	timestamp = ''.join(timestamp)
	lifetime = 3000 	# 30분 00초

	ticket = [IDc, addr, IDtgs, timestamp, lifetime]
	# 티켓 = [클라이언트ID, 클라이언트address, 티켓서버번호, 티켓발행시간, 티켓유효기간]

	ticket_pickle = pickle.dumps(ticket)
	# 티켓리스트를 시리얼라이즈
	
	# ticket_pickle += b'\x80'
	
	ticket_pickle = ticket_pickle.decode('utf-8', 'ignore')
	# des3.enc()에서 str타입으로 받으므로 타입변환

	des3 = mylib.MyDES3(key, iv)	# 암호화 하기 위해 객체 생성

	ticket_pickle_des3 = des3.enc(ticket_pickle)
	# 트리플DES로 티켓덤프를 암호화	

	ticket_pickle_des3_base64enc = base64.b64encode(ticket_pickle_des3)
	# 암호화 티켓을 전송하기위해 base64로 인코딩

	return ticket_pickle_des3_base64enc		

def main():
	host = ''
	port = 10100

	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	# 서버 소켓 = 전송할 주소체계 타입(IPv4, TCP)
	
	server.bind((host, port))
	# 소켓을 포트에 바인딩(매핑) 
	
	print(' [*] AS Server')
	print(' [*] Listening...')
	
	try:
		server.listen(1)    # 접속이 있을때까지 포트열고 대기
		conn, addr = server.accept()
		# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환

		print(' [+] Connect Client')
		print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
	
		recv_pickle = conn.recv(1024) 
		# 클라이언트가 티켓을 얻기 위한 정보(리스트덤프)

		recv_data = pickle.loads(recv_pickle)
		# 리스트덤프를 디시리얼라이즈
	
		if not authentication(recv_data[0], recv_data[1]):
			conn.close()	# 정보가 일치하지 않으면 연결종료
			print('user_ID or ticket_server do not match!!')
			sys.exit(1)
		
		ticket_pickle_des3_base64enc = makeTicket(recv_data[0], addr[0], recv_data[1])
		# 티켓을 만드는 과정		
	
		conn.send(ticket_pickle_des3_base64enc)
		# 인코딩된 티켓을 클라이언트에게 전송		

		conn.close()	# 클라이언트와 세션종료

	except KeyboardInterrupt:
		print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
		# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력		

	finally:
		print('AS_Server end')

if __name__ ==  '__main__':
	main()

 

[3] ticket_server.py

import socket
import pickle
import mylib
import base64
import sys
import time

# ticket_server = 10

def ticket_verify(ticket_tgs_pickle_des3_base64enc):
	key = 'kerberos_v2'
	iv = 'AS_Server'	
	
	des3 = mylib.MyDES3(key, iv)

	ticket_tgs_pickle_des3 = base64.b64decode(ticket_tgs_pickle_des3_base64enc)
	# base64 디코딩 후 저장

	ticket_tgs_pickle = des3.dec(ticket_tgs_pickle_des3)
	# 트리플 DES3로 복호화 후 저장
	
	ticket_tgs_pickle = ticket_tgs_pickle.encode()
	# byte타입으로 변환

	ticket_tgs_pickle = b'\x80'+ticket_tgs_pickle[:-3] + b'\xb8\x0be.' 
	# 트리플 DES3 복호화시 패딩관련 추가설정

	ticket_tgs = pickle.loads(ticket_tgs_pickle)
	# 티켓서버에 대한 티켓을 메모리에 로드

	return ticket_tgs

def authentication(IDc, addr, ticket_tgs):
	timestamp = [str(x) for x in time.localtime()[:6]]
	# 현재시간을 속성별로 리스트에 저장

	timestamp = int(''.join(timestamp))
	# timestamp를 int형으로 변환

	ticket_tgs[3] = int(ticket_tgs[3])
	# ticket_tgs에 저장된 timestamp도 int형으로 변환

	if IDc == ticket_tgs[0] and addr == ticket_tgs[1] and '10' == ticket_tgs[2] and timestamp - ticket_tgs[4] < ticket_tgs[3]:
		# ticket_tgs = [IDc, addr, IDtgs, Timestamp, Lifetime]

		# 분기문의 내용은 
		# 현재 티켓서버에 접속한 ID와 티켓에 적힌 ID가 동일한지
		# 현재 티켓서버에 접속한 IP주소와 티켓에 적힌 IP주소가 동일한지
		# 현재 티켓서버의 번호와 티켓에 적혀있는 서버의 번호가 동일한지
		# 티켓의 유효기간이 지나지 않았는지

		return True

	else:
		return False

def makeTicket(IDc, addr, IDv):
	key = 'kerberos_v2'
	iv = 'service_server'
	
	timestamp = [str(x) for x in time.localtime()[:6]]
	# 현재시간을 속성별로 리스트로 저장

	timestamp = ''.join(timestamp)
	lifetime = 3000		# 30분 00초

	ticket = [IDc, addr, IDv, timestamp, lifetime]
	# 티켓 = [클라이언트ID, 클라이언트address, 티켓서버번호, 티켓발행시>간, 티켓유효기간]

	ticket_pickle = pickle.dumps(ticket)
	# 티켓리스트를 시리얼라이즈

	ticket_pickle = ticket_pickle.decode('utf-8', 'ignore')
	# des3.enc()에서 str타입으로 받으므로 타입변환

	des3 = mylib.MyDES3(key, iv)    # 암호화 하기 위해 객체 생성

	ticket_pickle_des3 = des3.enc(ticket_pickle)
	# 트리플DES로 티켓덤프를 암호화 

	ticket_pickle_des3_base64enc = base64.b64encode(ticket_pickle_des3)
	# 암호화 티켓을 전송하기위해 base64로 인코딩

	return ticket_pickle_des3_base64enc


def main():
	host = ''
	port = 20100
	
	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	server.bind((host, port))

	print(' [*] Ticket Server')
	print(' [*] Listening...')

	try:
		server.listen(1)	# 접속이 있을때까지 포트열고 대기

		conn, addr = server.accept()
		# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환

		print(' [+] Connect Client')
		print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
		
		recv_pickle = conn.recv(1024) 
		# [클라이언트ID, 접속할서버번호, 티켓]의 리스트덤프 받기

		recv_data = pickle.loads(recv_pickle)
		# 받은 리스트덤프를 디시리얼라이즈		

		IDc, IDv, ticket_tgs_pickle_des3_base64enc = recv_data[0], recv_data[1], recv_data[2]	
		# 클라이언트ID와 티켓덤프를 암호화후 인코딩한 정보를 저장

		ticket_tgs = ticket_verify(ticket_tgs_pickle_des3_base64enc)
		# 티켓을 검증하는 과정

		if not authentication(IDc, addr[0], ticket_tgs):
			conn.close() 	# 정보가 일치하지 않으면 연결종료
			print('user_ID or address or service_number or ticket_valid_period do not match!!')
			sys.exit(1)

		ticket_pickle_des3_base64enc = makeTicket(IDc, addr[0], IDv)
		# 티켓을 만드는 과정 

		conn.send(ticket_pickle_des3_base64enc)
		# 인코딩된 티켓을 클라이언트에게 전송

		conn.close()	# 클라이언트와 세선종료
	
	except KeyboardInterrupt:
		print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
		# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력

	finally:
		print('ticket_server end')
		# 서버종료 시 출력

if __name__ ==  '__main__':
	main()

 

[4] service_server.py

import socket
import pickle
import mylib
import base64
import sys
import time
from datetime import datetime

def ticket_verify(ticket_pickle_des3_base64enc):
	key = 'kerberos_v2'
	iv = 'service_server'    
    
	des3 = mylib.MyDES3(key, iv) 

	ticket_pickle_des3 = base64.b64decode(ticket_pickle_des3_base64enc)
	# base64 디코딩 후 저장

	ticket_pickle = des3.dec(ticket_pickle_des3)
	# 트리플 DES3로 복호화 후 저장
    
	ticket_pickle = ticket_pickle.encode()
	# byte타입으로 변환

	ticket_pickle = b'\x80'+ticket_pickle[:-3] + b'\xb8\x0be.' 
	# 트리플 DES3 복호화 시 패딩관련 추가설정

	ticket = pickle.loads(ticket_pickle)
	# 티켓서버에 대한 티켓을 메모리에 로드
    
	return ticket


def authentication(IDc, addr, ticket):
	timestamp = [str(x) for x in time.localtime()[:6]]
	# 현재시간을 속성별로 리스트에 저장
	
	timestamp = int(''.join(timestamp))
	# timestamp를 int형으로 변환

	ticket[3] = int(ticket[3])
	# ticket에 저장된 timestamp도 int형으로 변환

	if IDc == ticket[0] and addr == ticket[1] and '100' == ticket[2] and timestamp - ticket[4] < ticket[3]:
		# ticket = [IDc, addr, IDv, Timestamp, Lifetime]

		# 분기문의 내용은 
		# 현재 서비스서버에 접속한 ID와 티켓에 적힌 ID가 동일한지
		# 현재 서비스서버에 접속한 IP주소와 티켓에 적힌 IP주소가 동일한지
		# 현재 서비스서버의 번호와 티켓에 적혀있는 서버의 번호가 동일한지
		# 티켓의 유효기간이 지나지 않았는지

		return True
	else:
		return False
	
def main():
	host = ''
	port = 30100 

	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	server.bind((host, port))

	try:
		print(' [*] Service Server')
		print(' [*] Listening...')
		server.listen(1)	# 접속이 있을때까지 포트열고 대기

		conn, addr = server.accept()
		# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환

		print(' [+] Connect Client')
		print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
		
		recv_pickle = conn.recv(1024) 
		# [클라이언트ID, 티켓]의 리스트덤프 받기

		recv_data = pickle.loads(recv_pickle)
		# 받은 리스트덤프를 디시리얼라이즈		

		IDc, ticket_pickle_des3_base64enc = recv_data[0], recv_data[1]	
		# 클라이언트ID와 티켓덤프를 암호화 후 인코딩한 정보를 저장

		ticket = ticket_verify(ticket_pickle_des3_base64enc)
		# 티켓을 검증하는 과정

		if not authentication(IDc, addr[0], ticket):
			conn.close() 	# 정보가 일치하지 않으면 연결종료
			print('user_ID or address or service_number or ticket_valid_period do not match!!')
			sys.exit(1)

		send_data = [datetime.now().year, datetime.now().month, datetime.now().day]
		# 시간정보를 리스트로 저장

		send_data_pickle = pickle.dumps(send_data)
		conn.send(send_data_pickle)

		conn.close()
	
	except KeyboardInterrupt:
		print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
		# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력

	finally:
		print('time_server end')
		# 서버종료 시 출력

if __name__ ==  '__main__':
	main()

 

소스에 대한 설명을 보태면

접속할 서버는 시간에 대한 서비스를 하는 서버입니다.

하하..

 


 

3-4) 커버로스 버전2[사용방법]

 

[1] [python3 AS_server.py] 명령어로 AS서버 동작


[2] [python3 ticket_server.py] 명령어로 ticket서버 동작


[3] [python3 service_server.py] 명령어로 service서버 동작


[4] [python3 client.py] 명령어로 클라이언트 동작


[5] 클라이언트에서 총 3개를 입력을 받음
 ▶ user_ID를 (user100)으로 입력
  ▶ ticket_server번호를 (10)으로 입력
  ▶ service_server번호를 (100)으로 입력


 - AS_server에서 검증 후 user_ID나 ticket_server번호가 일치하지 않을 경우 티켓발급 불가
 - user_ID나 ticket_server번호는 일치하고 service_server번호가 일치하지 않을 경우,
    AS_server에서 ticket_server의 티켓은 발급되지만 service_server에서 검증 후 서비스거부
 - 접속한 클라이언트 IP주소가 티켓의 IP주소가 다른경우 즉시 서버와 연결종료
 - 타임스탬프 확인 및 티켓 발행시간으로부터 30분이상 경과한 경우 티켓 무효화

 

와 같이 검증이 가능합니다.

 


 

3-5) 커버로스 버전2[시연]

 

[그림 6] Client_ID나 ticket_server의 번호가 다른경우(AS_Server입장) 

 

[그림 7] Client_ID나 ticket_server의 번호가 다른경우(Client입장)

 

[그림 8] service_server번호가 다른경우(Client입장)

 

[그림 9] Client_ID, service_server번호, 접속주소가 다르거나 티켓유효기간이 만료된 경우  

 

[그림 10] 정상적인 입력을 했을 경우

 

그렇다면 커버로스 버전2의 문제점은

무엇인지 알아볼까요?

 

티켓서버 티켓이나 접속할 서버의 티켓의

유효기간의 문제가 있다.

이는 재전송공격에 대한 위험을

가지고 있다.

 

서버를 사용자에게 인증하는 절차도 보이지 않는다.

티켓을 받은사람이랑 티켓을 제시하는 사람이 동일할까?

 

이를 해결하기 위해 상위버전의 커버로스에서는

AS와 TGS가 공유하는 비밀값(세션키)를 활용한다고 한다.

 

끄적끄적하다보니 생각보다 길어졌지만 

스크롤바가 여기에 있는 분들은 

커버로스를 습득하셨습니다!!

댓글