2025년 6월 5일 목요일

SSH Connection Layer에 사용자 패스워드 만료 검증 로직 추가

 개발 내역

다음과 같이 ssh 접속시 패스워드 변경 기능을 추가한다.


SSH Packet

먼저 SSH의 packet은 다음과 같이 구성되어 있다.

Packet LengthPadding LengthPayloadRandom PaddingMessage
Authentication Code
(MAC)
// ssh packet 형식
uint32   packet_length // payload + padding 길이 (4바이트)
byte     padding_length // padding 길이 (1바이트)
byte[n1] payload // 메시지 데이터 (메시지 타입 + 메시지 본문)
byte[n2] random padding // 암호화 블록 크기 맞추기용 랜덤 패딩
[MAC]    optional, if MAC is enabled // 메시지 인증 코드 (무결성 확인용, 선택적)

// 키 교환 패킷 예시 (SSH_MSG_KEXINIT)
packet_length:    0x000001fc
padding_length:   0x0c
payload:          (message type 20) + kex algorithms, hostkey algorithms, etc.
padding:          12 bytes of random data
MAC:              (only if enabled, e.g. hmac-sha2-256)

Wireshark로 캡처한 SSH 패킷이다.

HMAC은 세 가지 필드로 구성되어있다. 패딩, 페이로드, 랜덤 패딩의 내용은 암호화된 패킷 필드에 있다.




중요한 메시지 타입 (payload의 첫 바이트)

(https://www.iana.org/assignments/ssh-parameters/ssh-parameters.xhtml)

메세지 타입 번호메세지 이름설명
1SSH_MSG_DISCONNECT연결 종료
2SSH_MSG_IGNORE무시 가능한 패킷
20SSH_MSG_KEXINIT키 교환 초기화
21SSH_MSG_NEWKEYS새로운 키 적용
30~49키 교환 관련 메시지들ECDH 등 포함
50SSH_MSG_USERAUTH_REQUEST사용자 인증 요청
52SSH_MSG_USERAUTH_SUCCESS인증 성공
90SSH_MSG_CHANNEL_OPEN채널 열기
94SSH_MSG_CHANNEL_DATA터미널 데이터 송신



SSH Protocol Stack

SSH 는 다음과 같은 세가지 레이어로 구성되어있다. 


  • transport layer: 알고리즘 교섭(negotiation), 키 교환을 담당한다. (RFC 4253)
  • authentication layer: 사용자 인증을 담당한다. password(질문-답변), keyboard-interactive, public-key(공개키 기반) 등의 방법이 있다. (RFC 4252) 
  • connection layer: 실제로 터미널을 열거나 하는 과정을 담당 (RFC 4254)







SSH connection layer


1. SSH 연결이 설정되면, 클라이언트와 서버는 키 교환을 통해 안전한 통신 채널을 설정한다.

2. 클라이언트는 서버에 사용자 인증을 수행한다.

3. 인증이 성공하면 클라이언트는 SSH_MSG_CHANNEL_OPEN 메시지를 보내 새로운 채널을 열 수 있다.

byte      SSH_MSG_CHANNEL_OPEN
string    "session"
uint32    sender channel
uint32    initial window size
uint32    maximum packet size

4. 채널이 생성된 후 클라이언트가 SSH_MSG_CHANNEL_OPEN 메시지를 보내고, 서버가 이 요청을 수락하면, 클라이언트는 이제 해당 채널을 통해 데이터를 주고받을 수 있다.

  • SSH_MSG_CHANNEL_OPEN_CONFIRMATION       91
  • SSH_MSG_CHANNEL_OPEN_FAILURE            92

5. 클라이언트는 SSH_MSG_CHANNEL_REQUEST 메시지를 통해 특정 작업을 요청할 수 있다. (pty-req, shell)




Dropbear 를 이용한 SSH 서버 구성

Dropbear는 SSH 프로토콜의 경량 구현이다. SSH 서버는 Dropbear 기반으로 MatrixPKI(matrixssl에 포함된 버전으로)  또는 OpenSSL 호환 API로 구현하는 호환 레이어(cryptowrapper*)로 구성되어있다.


dropbear 커스텀 코드 분석

  • svr-main.c:
    • 연결을 받는 connection pool을 만드는 과정 → thread (event loop)로 대체
    • 실제로는 ipv4 / ipv6 socket 생성만 하고 끝나는 코드라 삭제 가능
    • 대신 bind(), listen() 등 옵션 세팅만 유지하는 형태로 개발
  • svr-chansession.c
    • shell 실행을 위한 channel session을 관리
    • svr-chantypes 는 session type 별 핸들러를 지정하는 구조. 여기서 &svrchansess 를 바꾸면 handler 를 교체하여 새로운 처리 방식 가능
    • 핵심은 channel request-handler 에서 connection 에 적절한 readfd /writefd를 설정해주고 실제 데이터 read/write 는 common-channel.c 에서 처리됨
    • 실제 channel operation 은 common-channel.c 에서 이루어짐
    • 비밀번호 수정 로직과 연결할 때 pipe 를 만들어 연결하면 dropbear 는 그것을 통해 사용자 입출력을 주고 받을 수 있다.
  • authentication
    • dropbear 는 기본적으로 public key 기반 인증을 지원하지만 기존 password 인증로직에 연결해야함

비밀번호 인증 절차

비밀 번호 인증 절차는 다음과 같은 절차로 이루어진다.

1. 클라이언트 -> 서버 (SSH_MSG_USERAUTH_REQUEST)

  • username, service, method(password, keyboard-interactive)을 서버에 요청

2. 서버 -> 클라이언트 - SSH_MSG_USERAUTH_INFO_REQUEST

3. 클라이언트 -> 서버 - SSH_MSG_USERAUTH_INFO_RESPONSE - password

byte      SSH_MSG_USERAUTH_REQUEST
string    user name
string    service name
string    "password"
boolean   FALSE
string    plaintext password in ISO-10646 UTF-8 encoding [RFC3629]

4. 서버 -> 클라이언트 - SSH_MSG_USERAUTH_SUCCESS


비밀번호 만료 로직 설계

비밀 번호 인증 로직을 기반으로 비밀번호 만료 로직을 다음과 같이 설계했다.


1. 클라이언트 -> 서버 - SSH_MSG_USERAUTH_REQUEST

2. 서버 -> 클라이언트 비밀 번호 요청 - SSH_MSG_USERAUTH_INFO_REQUEST

3. 클라이언트 -> 서버 - SSH_MSG_USERAUTH_INFO_RESPONSE

4. 서버 -> 클라이언트 비밀번호 만료, 새 비밀번호 요청 - SSH_MSG_USERAUTH_INFO_REQUEST

byte      SSH_MSG_USERAUTH_PASSWD_CHANGEREQ
string    prompt in ISO-10646 UTF-8 encoding [RFC3629]
string    language tag [RFC3066]

byte      SSH_MSG_USERAUTH_REQUEST
string    user name
string    service name
string    "password"
boolean   TRUE
string    plaintext old password in ISO-10646 UTF-8 encoding [RFC3629]
string    plaintext new password in ISO-10646 UTF-8 encoding [RFC3629]

5. 클라이언트 -> 서버 새 비밀번호를 두 번 입력 - SSH_MSG_USERAUTH_INFO_RESPONSE

6. 서버 -> 클라이언트 비밀번호 변경 성공 알림 - SSH_MSG_USERAUTH_INFO_REQUEST -> SSH_MSG_USERAUTH_SUCCESS


Dropbear SSH 서버 기반으로 비밀번호 만료 로직 개발

0. _dropbear_log, _dropbear_exit 등 callback 함수 치환

  • Dropbear 내부 로깅 함수와 종료 함수를 **시스템 로그 방식(zlog)**이나 커스터마이징된 종료 방식으로 대체
#define _dropbear_log(...) zlog_info(__VA_ARGS__)
#define _dropbear_exit(...) custom_exit_handler(__VA_ARGS__)


1. svr_session 구조를 통째로 쓰지 않고 필요한 부분만 직접 실행

  • svr_session은 Dropbear에서 세션 설정을 종합적으로 다루는 구조체지만,
  • 비밀번호 만료로직과 연동하는 방식이므로 필요한 부분만 직접 초기화하고 사용.
  • 예: ses.channel, ses.authstate, ses.sock_in, ses.remotehost 등만 수동 설정.

2. chaninitialise() 할 때 우리가 만든 핸들러로 교체

  • Dropbear는 chaninitialise()에서 여러 채널 타입 (session, x11, direct-tcpip)을 등록.
  • 여기서 svrchansess 핸들러를 우리가 만든 vty 기반 핸들러로 바꿔 등록.
svr_chantypes[CHAN_SESSION].inithandler = my_vty_chansess_init;


3. ses.packettypes에 custom handler 등록

  • SSH_MSG_USERAUTH_REQUEST → 사용자가 로그인 시 보내는 메시지
  • 해당 메시지에 대해 custom handler 등록
static const packettype svr_packettypes[] = {
  {SSH_MSG_USERAUTH_REQUEST, recv_msg_userauth_request}, /* server */

4. service_loop() 직접 호출

  • dropbear 는 내부적으로 event loop(service loop) 를 통해 패킷 처리 및 세션 유지
  • 기존 svr_main_loop() 에서 이 함수를 호출하는데 여기서는 직접 호출하여 흐름 제어

5. session 수동 생성

  • pipe 대신 socketpair(AF_UNIX, SOCK_STREAM) 사용
  • 이유: pipe 는 반이중, vty->sock 은 전이중(full-duplex)을 기대 → socketpaire 로 해결
int sv[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
  • 사용자 세션 정보를 갖는 터미널의 한쪽에 dropbear 의 channel에 연결

6. 인증 처리

  • SSH 접속 → USERAUTH_REQUEST → 비밀번호 변경로직

7. channel 과 세션 연결

  • 세션 생성 시 만든 socketpair의 다른 쪽을 channel의 read/write fd로 설정
  • 이후의 event loop는 thread로 대체
    • Dropbear는 select() 기반 polling 사용 → 이를 thread_add_read() 등으로 변경

댓글 없음:

댓글 쓰기

vrrp 장비 failover 시 sync 패킷 전송 중 panic 발생 오류

  vrrp 구성 (Active-Standby or Active-Active) 시 UTM 장비 failover 시 sync 패킷 전송 중 panic 발생하는 문제가 있었다. 외부 고객사에서 발생한 문제라 내부 재현은 불가능하여 코드 분석 레벨에서 se...