C program에 Shadow를 지원하도록 덧붙이는 것은 실제적으로 매우 간단하다. 단지
문제는 /etc/shadow
file에 접근하기 위해서는 program이 root(또는 SUID
root)로 실행되어야 한다는 것이다.
이 것은 커다란 문제 하나를 우리에게 강요한다: SUID program을 만들 때, 매우 조심스럽게 programming하는 습관이 되어 있어야 한다. 예를 들어, program이 shell 탈출기능을 가지고 있고 이 program이 SUID root라면, 이 기능이 root 권한을 주어서는 안된다.
password를 검사해 할 수 있지만 다른 경우는 root권한으로 실행할 필요가 없는
program에 shadow 지원 기능을 덧붙임으로써, SUID program보다 훨씬 안전한
program을 만들 수 있게 한다. xlock
program이 그 한 예이다.
아래 예에서, pppd-1.2.1d
는 이미 SUID root로 실행하고 있으므로,
shadow 지원 기능을 덧붙이는 것은 program이 더 취약하게 만들지 않을 것이다.
header file들은 /usr/include/shadow
에 있다.
또한, /usr/include/shadow.h
도 있다. 그러나, 이것은
/usr/include/shadow/shadow.h
에 대한 symbolic link일 것이다.
shadow 지원 기능을 추가하기 위해, header file을 넣자:
#include <shadow/shadow.h> #include <shadow/pwauth.h>
shadow code를 상황에 따라 compile하도록 compiler directive(지시자)를 쓰는 것은 종은 방법이다 (아래 예에서 보도록).
Shadow Suite을 설치할 때, libshadow.a
file은
/usr/lib
에 놓인다.
shadow 지원기능을 program에 넣을려면, linker에게 libshadow.a
를
같이 link하도록 지시해주어야 한다.
다음처럼:
gcc program.c -o program -lshadow
어쨌든, 아래 예에서 보다시피, 대부분 거대한 program들은 Makefile
을
사용하고, 우리가 고칠 LIBS=...
라는 변수를 대개 쓴다.
libshadow.a
library는 /etc/shadow
file로부터 얻는 정보를
spwd
라는 구조체에 담는다. spwd
구조체에 대한 정의는
/usr/include/shadow/shadow.h
file에 있다:
struct spwd { char *sp_namp; /* 사용자 이름 */ char *sp_pwdp; /* encrypt된 password */ sptime sp_lstchg; /* 최근 data 수정일 */ sptime sp_min; /* 수정작업간의 최소 날짜(결국 한번 수정한 다음 언제 수정이 가능한가에 대한 대답) */ sptime sp_max; /* 수정작업간의 최대 날짜(password 유효기간) */ sptime sp_warn; /* password가 무효가 되기 전 경고하는 기간 */ sptime sp_inact; /* password가 무효된 뒤, 계정이 사용불능이 될 때까지의 기간. */ sptime sp_expire; /* 날짜(계정사용불능 - 1/1/70) */ unsigned long sp_flag; /* 나중을 위해 비워둠 */ };
Shadow Suite는 sp_pwdp
field에 encode된 passwd와 함께 다른 걸
넣을 수 있다. password field는 다음처럼 될 수 있다:
username:Npge08pfz4wuk;@/sbin/extra:9479:0:10000::::
이는 password에 덧붙여, /sbin/extra
program이 더 심화된 인증을 위해
호출된다는 것을 의미한다. 호출되는 program은 username, 호출이유를 알려주는
switch를 받을 수 있어야 될 것이다. 자세한 걸 알고 싶다면,
/usr/include/shadow/pwauth.h
와 pwauth.c
를 보기 바란다.
이것이 의도하는 바는 -두번 사용자 확인하는 데 사용할 수도 있는- 다른
현존하는(actual) 사용자 확인 방법을 수행할 수 있도록 pwauth
기능을
쓰는 것이다.
Shadow Suite의 저자는 현존하는 대부분의 program들이 이 기능을 쓰고 있지 않음은 지적하면서, Shadow Suite 차기 version에는 사라지거나, 바뀔 것이라고 한다.
shadow.h
file은 libshadow.a
library에 있는 함수들의
기본형을 포함하고 있다:
extern void setspent __P ((void)); extern void endspent __P ((void)); extern struct spwd *sgetspent __P ((__const char *__string)); extern struct spwd *fgetspent __P ((FILE *__fp)); extern struct spwd *getspent __P ((void)); extern struct spwd *getspnam __P ((__const char *__name)); extern int putspent __P ((__const struct spwd *__sp, FILE *__fp));
예제에서 쓸 함수는: getspnam
- spwd
구조체에서 사용자 이름을
가져오는 함수 - 이다.
이것은 shadow 지원기능이 필요하지만 기본설정으로 되어 있지 않은 program에 그것을 추가하는 예제이다.
본 예제로, PAP이나 CHAP대신 /etc/passwd
file에 있는
사용자이름과 password를 사용하여 PAP 인증을 수행하는 mode를 지닌,
Point-to-Point Protocol Server (pppd-1.2.1d)를 들고 있다.
pppd의 이런 기능은 그리 자주 쓰이고 있지 않다. 그러나 Shadow Suite가
설치되면 이 기능은 못 쓰게 될 것이다. 왜냐하면 password는 더 이상
/etc/passwd
에 있지 않기 때문이다.
ppad-1.2.1d
에서 사용자 인증하는 code는
/usr/src/pppd-1.2.1d/pppd/auth.c
file에 있다.
다음 code는 #include
지시자가 위치하는 file의 윗부분에 덧댈 필요가
있다. 우리는 조건지시자(conditional directive)로 #include
를 둘러쌌다
(특별히 shadow 지원기능을 넣어 compile할 때만 포함하도록)
#ifdef HAS_SHADOW #include <shadow.h> #include <shadow/pwauth.h> #endif
다음은 실제 code를 고치는 일이다. 아직도 auth.c
file을 고치고 있다.
고치기 전의 auth.c
는:
/* * login - Check the user name and password against the system * password database, and login the user if OK. * * returns: * UPAP_AUTHNAK: Login failed. * UPAP_AUTHACK: Login succeeded. * In either case, msg points to an appropriate message. */ static int login(user, passwd, msg, msglen) char *user; char *passwd; char **msg; int *msglen; { struct passwd *pw; char *epasswd; char *tty; if ((pw = getpwnam(user)) == NULL) { return (UPAP_AUTHNAK); } /* * XXX If no passwd, let them login without one. */ if (pw->pw_passwd == '\0') { return (UPAP_AUTHACK); } epasswd = crypt(passwd, pw->pw_passwd); if (strcmp(epasswd, pw->pw_passwd)) { return (UPAP_AUTHNAK); } syslog(LOG_INFO, "user %s logged in", user); /* * Write a wtmp entry for this user. */ tty = strrchr(devname, '/'); if (tty == NULL) tty = devname; else tty++; logwtmp(tty, user, ""); /* Add wtmp login entry */ logged_in = TRUE; return (UPAP_AUTHACK); }
사용자 password는 pw->pw_passwd
에 위치한다. 따라서 할 일은
getspnam
함수를 추가하는 것이 전부다. 이 함수는
spwd->sp_pwdp
에 password를 할당한다.
우리는 다른 현존하는(actual) 사용자 확인 작업을 수행하도록 pwauth
함수를 넣을 것이다. 이는 shadow file에 설정되어 있으면 자동적으로 두번째
인증을 수행한다.
shadow를 지원하도록 고친 auth.c
는:
/* * login - Check the user name and password against the system * password database, and login the user if OK. * * This function has been modified to support the Linux Shadow Password * Suite if USE_SHADOW is defined. * * returns: * UPAP_AUTHNAK: Login failed. * UPAP_AUTHACK: Login succeeded. * In either case, msg points to an appropriate message. */ static int login(user, passwd, msg, msglen) char *user; char *passwd; char **msg; int *msglen; { struct passwd *pw; char *epasswd; char *tty; #ifdef USE_SHADOW struct spwd *spwd; struct spwd *getspnam(); #endif if ((pw = getpwnam(user)) == NULL) { return (UPAP_AUTHNAK); } #ifdef USE_SHADOW spwd = getspnam(user); if (spwd) pw->pw_passwd = spwd->sp-pwdp; #endif /* * XXX If no passwd, let NOT them login without one. */ if (pw->pw_passwd == '\0') { return (UPAP_AUTHNAK); } #ifdef HAS_SHADOW if ((pw->pw_passwd && pw->pw_passwd[0] == '@' && pw_auth (pw->pw_passwd+1, pw->pw_name, PW_LOGIN, NULL)) || !valid (passwd, pw)) { return (UPAP_AUTHNAK); } #else epasswd = crypt(passwd, pw->pw_passwd); if (strcmp(epasswd, pw->pw_passwd)) { return (UPAP_AUTHNAK); } #endif syslog(LOG_INFO, "user %s logged in", user); /* * Write a wtmp entry for this user. */ tty = strrchr(devname, '/'); if (tty == NULL) tty = devname; else tty++; logwtmp(tty, user, ""); /* Add wtmp login entry */ logged_in = TRUE; return (UPAP_AUTHACK); }
주의해서 보면 우리가 한 다른 변화를 볼 수 있을 것이다. /etc/passwd
file에 password가 없다면, 원 version은 UPAP_AUTHACK
를 돌려주고
접속을 허용했다. 이건 안 좋다. 왜냐하면, 이 login기능의 일반적인
용도는 PPP process에 접근한 다음, PAP에 의해 지원되는 사용자 이름과 password를
/etc/passwd
에 있는 사용자 이름과 /etc/shadow
에 있는
password와 맞는지 점검하도록 허용하는, 한 계정을 사용하는 것이기 때문이다.
따라서, 원 version이 사용자(특히, ppp
)를 위해 shell을 실행시키도록
설정했다면, 누구든지 그들의 PAP를 사용자이름을 ppp
, password를 null로
함으로써 ppp 연결을 획득할 수 있었다.
우리는 이것을 password가 없다면 UPAP_AUTHACK
대신
UPAP_AUTHNAK
를 되돌려주도록 고쳤다.
흥미롭게도 pppd-2.2.0
로 같은 문제를 지니고 있다.
다음은 두가지 일이 일어날 수 있도록 Makefile을 고지는 것이다:
USE_SHADOW
가 선언되어 있어야 하고, libshadow.a
가 link되도록
할 필요가 있다.
Makefile에서는:
LIBS = -lshadow
그리고나서 다음 줄을:
COMPILE_FLAGS = -I.. -D_linux_=1 -DGIDSET_TYPE=gid_t
에서:
COMPILE_FLAGS = -I.. -D_linux_=1 -DGIDSET_TYPE=gid_t -DUSE_SHADOW
로 바꾼다.
이제 만들어서 설치하라.