실습>
/root/rpmbuild/SOURCES/Linux-PAM-1.1.8/modules/pam_rootok
-- pam_rootok.c --
43 static int
44 _pam_parse (const pam_handle_t *pamh, int argc, const char **argv)
45 {
46 int ctrl=0;
47
48 printf("%s: _pam_parse() 실행!\n", __FILE__);
49
50 /* step through arguments */
51 for (ctrl=0; argc-- > 0; ++argv) {
157 PAM_EXTERN int
158 pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
159 int argc, const char **argv)
160 {
161 int ctrl;
162
163 printf("%s: pam_sm_authenticate() 실행!\n", __FILE__);
164 ctrl = _pam_parse(pamh, argc, argv);
165
166 return check_for_root (pamh, ctrl);
167 }
168
-- pam_rootok.c --
[root@localhost pam_rootok]# PAMCompile.sh
-- /etc/pam.d/su --
1 #%PAM-1.0
2 #auth requisite pam_deny.so
3 auth sufficient pam_rootok.so
-- /etc/pam.d/su --
3번 라인에 의해서 root 이므로 관리자로 변경되었다.
[root@localhost ~]# su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목 3월 30 09:36:50 KST 2023 일시 pts/2
[root@localhost ~]# exit
-- /etc/pam.d/su --
1 #%PAM-1.0
2 auth requisite pam_deny.so
3 auth sufficient pam_rootok.so
-- /etc/pam.d/su --
2번 라인의 requisite은 실패이면 무조건 인증이 실패하고 종료하므로 그 다음 모듈을 검사하지 않는다.
[root@localhost ~]# su -
su: 인증 실패
<-- pam_rootok.c: pam_sm_authenticate() 실행! 부분이 출력되지 않음.
-- /etc/pam.d/su --
1 #%PAM-1.0
2 auth requisite pam_permit.so <-- 성공이므로 다음 모듈인 pam_rootok.so를 실행한다.
3 auth sufficient pam_rootok.so
-- /etc/pam.d/su --
[root@localhost ~]# ps
PID TTY TIME CMD
1286 pts/2 00:00:00 bash
1632 pts/2 00:00:00 ps
[root@localhost ~]# su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목 3월 30 09:47:32 KST 2023 일시 pts/2
[root@localhost ~]# ps
PID TTY TIME CMD
1286 pts/2 00:00:00 bash
1633 pts/2 00:00:00 su
1634 pts/2 00:00:00 bash
1651 pts/2 00:00:00 ps
[root@localhost ~]# exit
pam_permit.c 수정
[root@localhost pam_permit]# pwd
/root/rpmbuild/SOURCES/Linux-PAM-1.1.8/modules/pam_permit
-- pam_permit.c --
35 PAM_EXTERN int
36 pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
37 int argc UNUSED, const char **argv UNUSED)
38 {
39 int retval;
40 const char *user=NULL;
41
42 printf(">>> %s : pam_sm_authenticate() 호출! <<<\n", __FILE__);
72 PAM_EXTERN int
73 pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
74 int argc UNUSED, const char **argv UNUSED)
75 {
76 printf(">>> %s : pam_sm_setcred() 호출! <<<\n", __FILE__);
77 return PAM_SUCCESS;
78 }
-- pam_permit.c --
[root@localhost pam_permit]# PAMCompile.sh
다른 터미널에서 su - 를 실행한다.
[root@localhost ~]# su -
>>> pam_permit.c : pam_sm_authenticate() 호출! <<< <-- requisite에 의해 성공을 했으므로
pam_rootok.c: pam_sm_authenticate() 실행! <-- pam_rootok.so 모듈을 실행한다.
pam_rootok.c: _pam_parse() 실행!
>>> pam_permit.c : pam_sm_setcred() 호출! <<<
마지막 로그인: 목 3월 30 10:04:32 KST 2023 일시 pts/1
[root@localhost ~]#
o auth sufficient pam_deny.so 일 경우
-- /etc/pam.d/su --
1 #%PAM-1.0
2 auth sufficient pam_deny.so
3 auth sufficient pam_rootok.so
-- /etc/pam.d/su --
[root@localhost pam_deny]# pwd
/root/rpmbuild/SOURCES/Linux-PAM-1.1.8/modules/pam_deny
-- pam_deny.c --
28 PAM_EXTERN int
29 pam_sm_authenticate(pam_handle_t *pamh UNUSED, int flags UNUSED,
30 int argc UNUSED, const char **argv UNUSED)
31 {
32 printf(">>> %s: pam_sm_authenticate() 호출! <<<\n", __FILE__);
33 return PAM_AUTH_ERR;
34 }
35
36 PAM_EXTERN int
37 pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
38 int argc UNUSED, const char **argv UNUSED)
39 {
40 printf(">>> %s: pam_sm_setcred() 호출! <<<\n", __FILE__);
41 return PAM_CRED_ERR;
42 }
-- pam_deny.c --
[root@localhost pam_deny]# PAMCompile.sh
다른 터미널에서 su - 를 실행한다.
[root@localhost ~]# su - user1
>>> pam_deny.c: pam_sm_authenticate() 호출! <<<
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
>>> pam_deny.c: pam_sm_setcred() 호출! <<<
마지막 로그인: 목 3월 30 09:47:22 KST 2023 일시 pts/2
[user1@localhost ~]$ exit
o auth sufficient pam_permit.so 일 경우
-- /etc/pam.d/su --
1 #%PAM-1.0
2 auth sufficient pam_permit.so <-- sufficient에 의해 성공을 했으므로
3 auth sufficient pam_rootok.so <-- pam_rootok.so를 실행하지 않고 인증에 성공한다.
-- /etc/pam.d/su --
[root@localhost ~]# su - user1
>>> pam_permit.c : pam_sm_authenticate() 호출! <<< <-- sufficient에 의해 성공을 했으므로
>>> pam_permit.c : pam_sm_setcred() 호출! <<< <-- pam_rootok.so를 실행하지 않고 인증에 성공한다.
마지막 로그인: 목 3월 30 10:24:34 KST 2023 일시 pts/1
[user1@localhost ~]$ exit <-- 그러므로 user1의 셸이 떨어진 것이다.
o auth required pam_deny.so 인 경우
-- /etc/pam.d/su --
1 #%PAM-1.0
2 auth required pam_deny.so <-- required 에 의해 실패를 했으므로
3 auth sufficient pam_rootok.so <-- 여기서 성공을 해도 최종적으로 인증은 실패가 된다.
-- /etc/pam.d/su --
[root@localhost ~]# passwd --stdin user1
user1 사용자의 비밀 번호 변경 중
111111
passwd: 모든 인증 토큰이 성공적으로 업데이트 되었습니다.
[root@localhost ~]# su - user1
>>> pam_deny.c: pam_sm_authenticate() 호출! <<<
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호: <-- 111111 를 입력한다.
>>> pam_deny.c: pam_sm_authenticate() 호출! <<<
su: 인증 실패
[root@localhost ~]#
o auth required pam_permit.so 인 경우
-- /etc/pam.d/su --
1 #%PAM-1.0
2 auth required pam_permit.so <-- required 에 의해 성공을 했지만 최종 성공은 아니고 다음 모듈을 본다.
3 auth sufficient pam_rootok.so <-- sufficient에 의해서 인증이 성공된다.
-- /etc/pam.d/su --
[root@localhost ~]# su - user1
>>> pam_permit.c : pam_sm_authenticate() 호출! <<< <-- required 에 의해 성공을 했지만 최종 성공을 아니다.
pam_rootok.c: pam_sm_authenticate() 실행! <-- sufficient에 의해서 root이므로 인증이 최종 성공된다.
pam_rootok.c: _pam_parse() 실행!
>>> pam_permit.c : pam_sm_setcred() 호출! <<<
마지막 로그인: 목 3월 30 10:42:10 KST 2023 일시 pts/1
마지막 로그인 실패: 목 3월 30 10:43:02 KST 2023 일시 pts/1
마지막 로그인 후 1 번의 로그인 시도가 실패하였습니다.
[user1@localhost ~]$
실습> pam_wheel.so
[root@localhost ~]# usermod -G wheel user1
[root@localhost ~]# usermod -G wheel user2
-- /etc/pam.d/su --
1 #%PAM-1.0
2 #auth required pam_permit.so
3 auth sufficient pam_rootok.so
4 # Uncomment the following line to implicitly trust users in the "wheel" group.
5 #auth sufficient pam_wheel.so trust use_uid
6 # Uncomment the following line to require a user to be in the "wheel" group.
7 auth required pam_wheel.so use_uid
8 auth substack system-auth
-- /etc/pam.d/su --
[root@localhost ~]# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목 3월 30 12:04:28 KST 2023 일시 pts/1
[user1@localhost ~]$ id
uid=1001(user1) gid=1001(user1) groups=1001(user1),10(wheel) ...(생략)
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목 3월 30 11:56:52 KST 2023 일시 pts/1
마지막 로그인 실패: 목 3월 30 11:57:33 KST 2023 일시 pts/1
[root@localhost ~]# exit
[user1@localhost ~]$ exit
[root@localhost ~]# su - user2
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목 3월 30 11:58:39 KST 2023 일시 pts/1
[user2@localhost ~]$ id
uid=1002(user2) gid=1002(user2) groups=1002(user2),10(wheel) ...(생략)
[user2@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목 3월 30 12:04:53 KST 2023 일시 pts/1
마지막 로그인 실패: 목 3월 30 12:07:32 KST 2023 일시 pts/1
[root@localhost ~]# exit
[user2@localhost ~]$ exit
실습> pam_succeed_if.so
if문으로 여러가지 값들을 비교할 때 사용하는 모듈
[root@localhost ~]# usermod -G wheel user1
[root@localhost ~]# usermod -G wheel user2
OPTION
- use_uid: 명령어를 실행하는 사용자를 의미한다.
-- /etc/pam.d/su --
1 #%PAM-1.0
2 #auth required pam_permit.so
3 auth sufficient pam_rootok.so <-- user2 사용자는 인증실패
4 # Uncomment the following line to implicitly trust users in the "wheel" group.
5 #auth sufficient pam_wheel.so trust use_uid
6 # Uncomment the following line to require a user to be in the "wheel" group.
7 # wheel(user1,user2)
8 # user1 사용자는 wheel 그룹에 포함되므로 root로 변경가능 O
9 # user2 사용자는 wheel 그룹에 포함되므로 root로 변경가능 O
10 auth required pam_succeed_if.so user = user1 use_uid <-- user2 사용자는 인증실패
11 auth required pam_wheel.so use_uid <-- user2 사용자는 인증성공
12 auth substack system-auth <-- 비밀번호 입력 user2 사용자는 인증성공을 했지만 10번 때문에 최종 실패
>>> 12번 위에 required 부분 10번에 실패가 있기 때문에 12번에서 최종 인증에 실패하므로 로그인이 안된다. <<<
>>> 12번에서 입력된 root의 비밀번호 검증이 맞으면 12번 위에 10번과 11번의 required 부분에 <<<
>>> 실패가 있는지 확인하고 실패가 있다면 12번에서 최종 인증에 실패하므로 root로 로그인할 수 없다. <<<
-- /etc/pam.d/su --
[root@localhost ~]# su - user2
[user2@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
su: 인증 실패
[user2@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
su: 인증 실패
[user2@localhost ~]$ exit
-- /etc/pam.d/su --
1 #%PAM-1.0
2 #auth required pam_permit.so
3 auth sufficient pam_rootok.so <-- user1 사용자는 인증실패
4 # Uncomment the following line to implicitly trust users in the "wheel" group.
5 #auth sufficient pam_wheel.so trust use_uid
6 # Uncomment the following line to require a user to be in the "wheel" group.
7 # wheel(user1,user2)
8 # user1 사용자는 wheel 그룹에 포함되므로 root로 변경가능 O
9 # user2 사용자는 wheel 그룹에 포함되므로 root로 변경가능 O
10 auth required pam_succeed_if.so user = user1 use_uid <-- user1 사용자는 인증성공
11 auth required pam_wheel.so use_uid <-- user1 사용자는 인증성공
12 auth substack system-auth <-- user1 사용자는 비밀번호 검증에 성공을 했고 10,11번을 체크한 후 최종 성공
>>> 12번에서 입력된 root의 비밀번호 검증이 맞으면 12번 위에 10번과 11번의 required 부분에 <<<
>>> 실패가 없는지 확인하고 실패가 없다면 12번에서 최종 인증에 성공하므로 root로 로그인할 수 있다. <<<
-- /etc/pam.d/su --
[root@localhost ~]# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목 3월 30 12:04:40 KST 2023 일시 pts/1
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목 3월 30 12:22:31 KST 2023 일시 pts/1
마지막 로그인 실패: 목 3월 30 12:31:54 KST 2023 일시 pts/1
마지막 로그인 후 4 번의 로그인 시도가 실패하였습니다.
[root@localhost ~]#
실습> 라이브러리 경로 변경하기
-- /etc/pam.d/su --
11 auth required /libmydir/pam_wheel.so use_uid
12 auth substack system-auth
-- /etc/pam.d/su --
# mkdir /libmydir
# mv /usr/lib64/security/pam_wheel.so /libmydir/
# yum -y install tree
# tree /libmydir/
/libmydir/
└── pam_wheel.so
0 directories, 1 file
# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목 3월 30 14:57:16 KST 2023 일시 pts/0
[user1@localhost ~]$
[user1@localhost ~]$ id
uid=1001(user1) gid=1001(user1) groups=1001(user1),10(wheel) ...(생략)
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목 3월 30 14:57:19 KST 2023 일시 pts/0
[root@localhost ~]# exit
[user1@localhost ~]$ exit
라이브러리 경로를 원래대로 돌려놓는다.
- 모듈의 경로가 상대경로 이므로 /usr/lib64/security에서 찾는다.
[root@localhost ~]# vi /etc/pam.d/su
11 auth required pam_wheel.so use_uid
12 auth substack system-auth
[root@localhost ~]# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목 3월 30 15:14:57 KST 2023 일시 pts/0
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
su: 모듈을 알 수 없음 <-- /usr/lib64/security/pam_wheel.so 파일이 없기 때문에 에러가 발생한 것이다.
[user1@localhost ~]$ exit
pam_wheel.so 파일을 원래 디렉터리인 /usr/lib64/security 디렉터리에 이동시킨다.
[root@localhost ~]# mv /libmydir/pam_wheel.so /usr/lib64/security/
[root@localhost ~]# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목 3월 30 15:17:08 KST 2023 일시 pts/0
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목 3월 30 15:15:32 KST 2023 일시 pts/0
마지막 로그인 실패: 목 3월 30 15:17:14 KST 2023 일시 pts/0
마지막 로그인 후 1 번의 로그인 시도가 실패하였습니다.
[root@localhost ~]# <-- 정상적으로 root로 변경된 것을 확인할 수 있다.
실습> salt값에 의한 해쉬값 확인하기
# echo 1 | passwd --stdin root
# head -1 /etc/shadow
root:$6$oE7VchFp72O2w2Oi$Y1UdgnitPA04bqADoVKWdEGK3SRRteUMzY4HHVWpDgvXAE3YVaxWeI0yqltU9eYypdCxfdhoAKje9wNioyK0p1:19310:0:99999:7:::
# echo 1 | passwd --stdin root
# head -1 /etc/shadow
root:$6$RLIGFZB9Bkhmteiz$.TXpkv6jElyuWeXOlQZRB2RNGGxElVB/TsJ84N.Z0MwQc02FBeBUlEq0O7besapj13lseDYZU0ughSXMpwx8B0:19310:0:99999:7:::
# echo 1 | passwd --stdin root
# head -1 /etc/shadow
root:$6$4DF2Zt1qlu8SEXUp$X4aHQJvEB47WgbhBGpgEjf8TaxjB6wxxSWye0YFzFDU5vCO6toL00hlQ7fDmo2yZniwPy8CNwUCjsRLJY7Irw/:19310:0:99999:7:::
# echo 1 | passwd --stdin root
# head -1 /etc/shadow
root:$6$jq0qV0QeVhD/AZuA$CbPvUqdomVPtX5byvVuX3JHFcWgLRtEFYxjG5Jx/KH/.UqVZ9KpCEqdv2zoNQa394/.y.cigOOWpBC8OHBGUz/:19310:0:99999:7:::
실습> crypt() 함수 사용하기
crypt(): Linux에서 비밀번호 해쉬값을 만들어주는 함수
/etc/shadow: 두 번째 필드에 암호화된 해쉬값(비밀번호)이 저장되어 있다.
형식: $암호화방식$salt$암호화된해쉬값
해쉬값은 해쉬함수에 의해서 만들어진 값으로 임의의 문자열(개수가 정해지지 않음)을 넣으면 고정길이로 뽑아주는 함수
해쉬는 단반향 암호화이므로 역으로 풀 수 없다. 풀 수 있다면 암호화가 깨진 것이다.
1. 사용자 생성
passuser 사용자를 생성한다.
/etc/shadow 파일에 저장된 비밀번호는 각 시스템마다 모두 다르다.
# useradd passuser
# tail -1 /etc/shadow
passuser:!!:19093:0:99999:7:::
~~
2. 비밀번호 확인
# echo 111111 | passwd --stdin passuser
# tail -1 /etc/shadow
passuser:$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9FDYD2Yh5on5636FCj8BMjY4jfrY0qyeCCCWpSqvOq1xyAXp.:19446:0:99999:7:::
3. 프로그램 작성
# yum -y install man man-pages gcc make
# vi pass.c
/*
* 파일명: pass.c
* 프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
* 작성자: 리눅스마스터넷
*
* char *crypt(const char *key, const char *salt);
* 컴파일 방법: gcc -o pass pass.c -lcrypt
*/
#include<stdio.h>
#include<string.h>
#include<crypt.h>
int main()
{
char pPass[] = "111111";
char pSalt[] = "$6$5dzvDOsn$"; // 각자 다르므로 자신의 것으로 설정한다.
char *pHash;
pHash = crypt(pPass,pSalt);
printf("%s \n",pHash);
return 0;
}
-l라이브러리명 : crypt 라이브러리
# ll /usr/lib64/libcrypt*
# ll /usr/lib64/libcrypt-2.17.so
# gcc -o pass pass.c -lcrypt
# ldd pass
linux-vdso.so.1 => (0x00007fff10baf000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fafbc0d8000) <-- crypt 라이브러리
libc.so.6 => /lib64/libc.so.6 (0x00007fafbbd0a000)
libfreebl3.so => /lib64/libfreebl3.so (0x00007fafbbb07000)
/lib64/ld-linux-x86-64.so.2 (0x00007fafbc30f000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fafbb903000)
4. 실행
실행파일 pass를 실행해서 /etc/shadow에 저장된 passuser와 암호가 동일한지 확인한다.
# tail -1 /etc/shadow
passuser:$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9FDYD2Yh5on5636FCj8BMjY4jfrY0qyeCCCWpSqvOq1xyAXp.:19446:0:99999:7:::
# tail -1 /etc/shadow|awk -F : '{print $2}'
$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9FDYD2Yh5on5636FCj8BMjY4jfrY0qyeCCCWpSqvOq1xyAXp.
# ./pass
$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9FDYD2Yh5on5636FCj8BMjY4jfrY0qyeCCCWpSqvOq1xyAXp.
실습> crypt() 함수 사용하기
1. 소스코드 작성
# vi pass2.c
/*
* 파일명: pass2.c
* 프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
* 작성자: 리눅스마스터넷
*
* char *crypt(const char *key, const char *salt);
* 컴파일 방법: gcc -o pass2 pass2.c -lcrypt
*/
#include<stdio.h>
#include<string.h>
#include<crypt.h>
int main(int argc, char *argv[])
{
if(argc != 2) {
fprintf(stderr, "Usage: %s password\n", argv[0]);
return 1;
}
// passuser:$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9 ...(생략)
char *pPass = argv[1];
char pSalt[] = "$6$5dzvDOsn$"; // 각자 다르므로 grep passuser /etc/shadow 로 확인한다.
char *pHash;
pHash = crypt(pPass,pSalt);
printf("passuser:%s \n",pHash);
return 0;
}
2. 컴파일
컴파일할 때 -lcrpyt 라이브러리를 포함시킨다.
라이브러리 위치: /usr/lib64, /lib64(/usr/lib64 심볼릭 링크)
# gcc -o pass2 pass2.c -lcrypt
3. 실행
# grep passuser /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
# ./pass2 111111
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.
사용자 passuser2를 생성하고 비밀번호를 111111 로 설정한다.
# useradd passuser2
# passwd --stdin passuser2
passuser2 사용자의 비밀 번호 변경 중
111111
passwd: 모든 인증 토큰이 성공적으로 업데이트 되었습니다.
# grep passuser2 /etc/shadow
passuser2:$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.:19435:0:99999:7:::
pass2.c 의 소스에서 아래 두 군데만 passuser2 로 변경한다. 변경한다.
-- pass2.c 의 수정된 소스 --
char pSalt[] = "$6$2AO8Wuo7$"; // passuser2의 salt 값으로 변경한다.
printf("passuser2:%s \n",pHash);
-- pass2.c 의 수정된 소스 --
# gcc -o pass2 pass2.c -lcrypt
# grep passuser2 /etc/shadow
passuser2:$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.:19435:0:99999:7:::
# ./pass2 111111
passuser2:$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.
실습> salt 값이 없는 경우
1. 소스코드 작성
# vi pass3.c
/*
* 파일명: pass3.c
* 프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
* 작성자: 리눅스마스터넷
*
* char *crypt(const char *key, const char *salt);
* 컴파일 방법: gcc -o pass3 pass3.c -lcrypt
*/
#include<stdio.h>
#include<string.h>
#include<crypt.h>
int main(int argc, char *argv[])
{
if(argc != 2) {
fprintf(stderr, "Usage: %s password\n", argv[0]);
return 1;
}
char *pPass = argv[1];
char pSalt[] = "$6$"; // salt 값이 없는 경우
char *pHash;
pHash = crypt(pPass,pSalt);
printf("%s \n",pHash);
return 0;
}
2. 컴파일
# gcc -o pass3 pass3.c -lcrypt
3. 실행
salt의 추가 첨가물 문자열이 없다면 해쉬값은 동일하게 나온다.
# ./pass3 111111
$6$$2/5CYZn./k1Kv4XDyvESqncw9V8t.hF3Ah7VtjJXlOM9ao4r9S79dvNnYa4LVRA8pUzrdymGll1PY245M9J8e.
# ./pass3 111111
$6$$2/5CYZn./k1Kv4XDyvESqncw9V8t.hF3Ah7VtjJXlOM9ao4r9S79dvNnYa4LVRA8pUzrdymGll1PY245M9J8e.
# ./pass3 111111
$6$$2/5CYZn./k1Kv4XDyvESqncw9V8t.hF3Ah7VtjJXlOM9ao4r9S79dvNnYa4LVRA8pUzrdymGll1PY245M9J8e.
실습> strcat 함수 사용하기
함수의 원형:
#include <string.h>
char *strcat(char *dest, const char *src);
함수 기능:
src 값을 dest에 연결한다.
리턴값: dest
# vi strcatTest.c
#include <stdio.h>
#include <string.h>
int main()
{
// char *strcat(char *dest, const char *src);
char src[50] = "ABC";
char dest[50] = "12345";
printf("%s\n", strcat(dest, src)); // 12345ABC
printf("src: %s\n", src); // ABC
printf("dest: %s\n", dest); // 12345ABC
return 0;
}
# gcc -o strcatTest strcatTest.c
# ./strcatTest
12345ABC
src: ABC
dest: 12345ABC
# gdb strcatTest
Reading symbols from /root/strcatTest...done.
(gdb) b main
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048478 in main at strcatTest.c:7
(gdb) r
7 char src[50] = "ABC";
(gdb) n
8 char dest[50] = "12345";
(gdb) n
10 printf("%s\n", strcat(dest, src)); // 12345ABC
(gdb) p src
$1 = "ABC", '\000' <repeats 46 times>
(gdb) p dest
$2 = "12345", '\000' <repeats 44 times>
(gdb) p &src
$3 = (char (*)[50]) 0xffffd5fe
(gdb) p &dest
$4 = (char (*)[50]) 0xffffd5cc
(gdb) x/16xw &src
0xffffd5fe: 0x00434241 0x00000000 0x00000000 0x00000000
0xffffd60e: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd61e: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd62e: 0xe0000000 0x0000f7fc 0x00000000 0x12d30000
(gdb) x/xb 0xffffd5fe
0xffffd5fe: 0x41
(gdb) x/xb 0xffffd5ff
0xffffd5ff: 0x42
(gdb) x/xb 0xffffd600
0xffffd600: 0x43
(gdb) x/xb 0xffffd601
0xffffd601: 0x00
(gdb) x/16xw 0xffffd5cc
0xffffd5cc: 0x34333231 0x00000035 0x00000000 0x00000000
0xffffd5dc: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd5ec: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd5fc: 0x42410000 0x00000043 0x00000000 0x00000000
(gdb) x/xb 0xffffd5cc
0xffffd5cc: 0x31
(gdb) x/xb 0xffffd5cd
0xffffd5cd: 0x32
(gdb) x/xb 0xffffd5ce
0xffffd5ce: 0x33
(gdb) x/xb 0xffffd5cf
0xffffd5cf: 0x34
(gdb) x/xb 0xffffd5d0
0xffffd5d0: 0x35
(gdb) x/xb 0xffffd5d1
0xffffd5d1: 0x00
(gdb) n
12345ABC
12 printf("src: %s\n", src); // ABC
(gdb) x/16xw &src
0xffffd5fe: 0x00434241 0x00000000 0x00000000 0x00000000
0xffffd60e: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd61e: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd62e: 0xe0000000 0x0000f7fc 0x00000000 0x12d30000
(gdb) x/16xw &dest
0xffffd5cc: 0x34333231 0x43424135 0x00000000 0x00000000
0xffffd5dc: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd5ec: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd5fc: 0x42410000 0x00000043 0x00000000 0x00000000
(gdb) c
Continuing.
src: ABC
dest: 12345ABC
[Inferior 1 (process 1407) exited normally]
(gdb) q
실습> 동적으로 암호와 salt를 입력 받아서 사용하기
1. 소스 코드 작성
# vi pass4.c
/*
* 파일명: pass4.c
* 프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
* 작성자: 리눅스마스터넷
*
* char *crypt(const char *key, const char *salt);
* 컴파일 방법: gcc -o pass4 pass4.c -lcrypt
*/
#include<stdio.h>
#include<string.h>
#include<crypt.h>
int main(int argc, char *argv[])
{
if(argc != 3) {
fprintf(stderr, "Usage: %s password salt\n", argv[0]);
return 1;
}
char *pPass = argv[1];
char pSalt[20] = "$6$";
char *pHash;
strcat(pSalt, argv[2]);
pHash = crypt(pPass,pSalt);
printf("%s \n",pHash);
return 0;
}
2. 컴파일
# gcc -m32 -g -o pass4 pass4.c -lcrypt
3. 실행
# ./pass4
Usage: ./pass4 password salt
# ./pass4 111111
Usage: ./pass4 password salt
테스트는 직접 본인의 비밀번호로 작업한다.
# tail -1 /etc/shadow|awk -F : '{print $2}'
$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.
# ./pass4 111111 2AO8Wuo7
$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.
# ./pass4 111111 jp3hK011
$6$jp3hK011$hgouaAnJ8TBWVlmD8EcU7s6f2utk7hkEfKH.WTevrZfBrVTqU5T8Ja8hdZ9EqqyD50Ivm4VXl6SzVMCkC6jPK.
실습> 파이썬으로 변경하기
python crypt() 함수
https://docs.python.org/ko/3/library/crypt.html
파일명:
pass.c -> pass.py
pass4.c -> pass4.py
파이썬 가상환경을 만들고 소스코드를 만든다.
1. 파이썬 3.8 설치
yum -y install centos-release-scl
yum -y install rh-python38
scl enable rh-python38 bash
echo 'scl enable rh-python38 bash' >> ~/.bash_profile
python --version
2. 가상환경 생성
# python -m venv cryptProject
# . cryptProject/bin/activate
(cryptProject) [root@localhost ~]#
(cryptProject) [root@localhost ~]# python -m pip install --upgrade pip
(cryptProject) [root@localhost ~]# python -m pip list
Package Version
---------- -------
pip 23.0.1
setuptools 41.6.0
pass.c -> pass.py 변경하기
(cryptProject) [root@localhost ~]# vi pass.py
#!/usr/bin/env python
"""
파일명: pass.py
프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
작성자: 리눅스마스터넷
"""
import crypt
pPass = "111111" # 비밀번호
pSalt = "$6$qh/lKXOk$" # /etc/shadow 파일에서 각자 자신의 설정된 값으로 설정한다.
print(f"passuser:{crypt.crypt(pPass,pSalt)}")
(cryptProject) [root@localhost ~]# tail -1 /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
(cryptProject) [root@localhost ~]# chmod 755 pass.py
(cryptProject) [root@localhost ~]# ./pass.py
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.
(cryptProject) [root@localhost ~]# grep passuser /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
pass4.c -> pass4.py 변경하기
(cryptProject) [root@localhost ~]# vi pass4.py
#!/usr/bin/env python
"""
파일명: pass4.py
프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
작성자: 리눅스마스터넷
"""
import crypt
import sys
argc = len(sys.argv)
if argc != 3:
print(f"Usage: {sys.argv[0]} password salt", file=sys.stderr)
sys.exit(1)
pPass = sys.argv[1]
pSalt = "$6$" + sys.argv[2]
pHash = crypt.crypt(pPass,pSalt)
print(f"passuser:{pHash}")
(cryptProject) [root@localhost ~]# chmod 755 pass4.py
(cryptProject) [root@localhost ~]# grep passuser /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
(cryptProject) [root@localhost ~]# ./pass4.py
Usage: ./pass4.py password salt
(cryptProject) [root@localhost ~]# ./pass4.py 111111
Usage: ./pass4.py password salt
(cryptProject) [root@localhost ~]# ./pass4.py 111111 qh/lKXOk
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.
실습> 사전 파일 공격 프로그램 작성하기
1. 사용자 생성
passuser 사용자를 생성한다.
# useradd passuser
# echo 111111 | passwd --stdin passuser
2. 비밀번호 확인
passuser 계정의 비번을 확인한다.
# grep passuser /etc/shadow
passuser:$6$LSOx7Bx.$O2KVaIXsboxexxrLxfp2jayeXt.YBCbbwQmz5n5zEiIkDgROxnRmplahQm6wVD8KHJbbNbywhlvINOhC9sLJq1:19093:0:99999:7:::
3. 프로그램 작성
vi에서 붙여넣기할 때 :set noai, :set paste를 설정하고 붙여넣는다.
# vi dictAttack.c
/*
* 파일명: dictAttack.c
* 프로그램 설명: 사전파일 공격
* 작성자: 리눅스마스터넷
* 컴파일: gcc -m32 -g -o dictAttack dictAttack.c -lcrypt
*/
#include <stdio.h>
#include <string.h>
#include <crypt.h>
int main()
{
// /etc/shadow에서 각자의 비밀번호 값으로 채운다.
// grep passuser /etc/shadow
// passuser 비밀번호: 111111
char hash[] = "$6$LSOx7Bx.$O2KVaIXsboxexxrLxfp2jayeXt.YBCbbwQmz"
"5n5zEiIkDgROxnRmplahQm6wVD8KHJbbNbywhlvINOhC9sLJq1";
char salt[] = "$6$LSOx7Bx.";
char word[20] = "\0";
char *result;
int count = 1;
int found = 0;
FILE *fp = fopen("dict.txt","r");
if(fp == NULL)
{
fprintf(stderr, "[-] dict.txt 파일이 없습니다.\n");
return 1;
}
while(fscanf(fp,"%s",word) != EOF)
{
#ifdef DEBUG
printf("Debug: %s\n", word);
#endif
result = crypt(word, salt);
if(strcmp(result, hash) == 0) // 비교해서 맞으면
{
printf(">>> count : %d <<<\n", count);
printf("[*] password is: %s\n" , word);
found = 1;
break;
}
count++;
}
if(!found) // 비밀번호를 찾지 못했다면
printf("password not found!\n");
fclose(fp);
return 0;
}
4. 컴파일/실행
# gcc -g -o dictAttack dictAttack.c -DDEBUG -lcrypt
# ./dictAttack
[-] dict.txt 파일이 없습니다.
5. 사전 파일 생성
dict.txt 이름으로 사전 파일을 생성한다.
# vi dict.txt
1
11
111
1111
11111
111111
1111111
a
aa
aaa
6. 실행
# ./dictAttack
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
>>> count : 6 <<<
[*] password is: 111111
# gcc -m32 -g -o dictAttack dictAttack.c -lcrypt
# ./dictAttack
>>> count : 6 <<<
[*] password is: 111111
7. 비번 변경
# echo aaa | passwd --stdin passuser
# grep passuser /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
실습> 디버깅으로 분석하기
# gdb dictAttack
Reading symbols from /root/dictAttack...done.
(gdb)
(gdb) b main
Breakpoint 1 at 0x804860c: file dictAttack.c, line 17.
(gdb) r
Starting program: /root/dictAttack
Breakpoint 1, main () at dictAttack.c:17
17 char hash[] = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51"
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.i686 nss-softokn-freebl-3.79.0-4.el7_9.i686
(gdb) n
19 char salt[] = "$6$qh/lKXOk$";
(gdb) n
20 char word[20] = "\0";
(gdb) n
22 int count = 1;
(gdb) n
23 int found = 0;
(gdb) n
25 FILE *fp = fopen("dict.txt","r");
(gdb) p hash
$1 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) p salt
$2 = "$6$qh/lKXOk$"
(gdb) p count
$3 = 1
(gdb) p found
$4 = 0
(gdb) p fp
$5 = (FILE *) 0xffffd544
(gdb) p *fp
$6 = {_flags = -10605, _IO_read_ptr = 0x0, _IO_read_end = 0xffffd6a4 "MANPATH=/opt/rh/rh-python38/root/usr/share/man:",
_IO_read_base = 0xffffd6d4 "XDG_SESSION_ID=1", _IO_write_base = 0xffffd6e5 "HOSTNAME=localhost.localdomain",
_IO_write_ptr = 0xffffd704 "SELINUX_ROLE_REQUESTED=", _IO_write_end = 0xffffd71c "TERM=xterm",
_IO_buf_base = 0xffffd727 "SHELL=/bin/bash", _IO_buf_end = 0xffffd737 "HISTSIZE=1000",
_IO_save_base = 0xffffd745 "SSH_CLIENT=200.200.200.1 50137 22", _IO_backup_base = 0xffffd767 "SELINUX_USE_CURRENT_RANGE=",
_IO_save_end = 0xffffd782 "X_SCLS=rh-python38 ", _markers = 0xffffd796, _chain = 0xffffd7a9, _fileno = -10317, _flags2 = -10 266,
_old_offset = -8830, _cur_column = 56718, _vtable_offset = -1 '\377', _shortbuf = "\377", _lock = 0xffffddad, _offset = -369 36718754361,
__pad1 = 0xffffde76, __pad2 = 0xffffde80, __pad3 = 0xffffde91, __pad4 = 0xffffdeaa, __pad5 = 4294958771, _mode = -8502,
_unused2 = "\322\336\377\377\335\336\377\377\352\336\377\377\067\337\377\377k\337\377\377\250\337\377\377\313\337\377\377\00 0\000\000\000 \000\000\000 \224\375", <incomplete sequence \367>}
(gdb) n
26 if(fp == NULL)
(gdb) p *fp
$7 = {_flags = -72539000, _IO_read_ptr = 0x0, _IO_read_end = 0x0, _IO_read_base = 0x0, _IO_write_base = 0x0, _IO_write_ptr = 0 x0,
_IO_write_end = 0x0, _IO_buf_base = 0x0, _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0,
_markers = 0x0, _chain = 0xf7f9d980 <_IO_2_1_stderr_>, _fileno = 7, _flags2 = 0, _old_offset = 0, _cur_column = 0,
_vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x804b0a0, _offset = -1, __pad1 = 0x0, __pad2 = 0x804b0ac, __pad3 = 0x0, __pad4 = 0x0,
__pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 39 times>}
(gdb) p fp
$8 = (FILE *) 0x804b008
(gdb) n
32 while(fscanf(fp,"%s",word) != EOF)
(gdb) n
38 result = crypt(word, salt);
(gdb) p word
$9 = "1", '\000' <repeats 18 times>
(gdb) x/16xw &word
0xffffd3fc: 0x00000031 0x00000000 0x00000000 0x00000000
0xffffd40c: 0x00000000 0x71243624 0x4b6c2f68 0x246b4f58
0xffffd41c: 0x24362400 0x6c2f6871 0x6b4f584b 0x4f556824
0xffffd42c: 0x4c4b3051 0x35305759 0x465a654f 0x766b6b4f
(gdb) n
39 if(strcmp(result, hash) == 0) // 비교해서 맞으면
(gdb) p result
$10 = 0x804b170 "$6$qh/lKXOk$ZxAnywAnw6ypFIMpdThNUIel/ijRDX.kn4kHVVVLzyXouhUczi/PmjRCcDo3clbV59CTtKH3/tBC1ZSNnqTJ8/"
(gdb) p hash
$11 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
46 count++;
(gdb) p count
$12 = 1
(gdb) n
32 while(fscanf(fp,"%s",word) != EOF)
(gdb) p count
$13 = 2
(gdb) n
38 result = crypt(word, salt);
(gdb) p word
$14 = "11", '\000' <repeats 17 times>
(gdb) x/16xw word
0xffffd3fc: 0x00003131 0x00000000 0x00000000 0x00000000
0xffffd40c: 0x00000000 0x71243624 0x4b6c2f68 0x246b4f58
0xffffd41c: 0x24362400 0x6c2f6871 0x6b4f584b 0x4f556824
0xffffd42c: 0x4c4b3051 0x35305759 0x465a654f 0x766b6b4f
(gdb) x/16xw count
0x2: Cannot access memory at address 0x2
(gdb) n
39 if(strcmp(result, hash) == 0) // 비교해서 맞으면
(gdb) n
46 count++;
(gdb) n
32 while(fscanf(fp,"%s",word) != EOF)
(gdb) n
38 result = crypt(word, salt);
(gdb) x/16xw word
0xffffd3fc: 0x00313131 0x00000000 0x00000000 0x00000000
0xffffd40c: 0x00000000 0x71243624 0x4b6c2f68 0x246b4f58
0xffffd41c: 0x24362400 0x6c2f6871 0x6b4f584b 0x4f556824
0xffffd42c: 0x4c4b3051 0x35305759 0x465a654f 0x766b6b4f
(gdb) n
39 if(strcmp(result, hash) == 0) // 비교해서 맞으면
(gdb) p result
$15 = 0x804b170 "$6$qh/lKXOk$D9nudUorSMOgfT6jCp3IIAAF.5V3MpiIGepdDcUYBU2NxSREtu.ea34tNuNGbuv.kQ3pEYfxwOs4oyG0nkcVX/"
(gdb) p hash
$16 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
46 count++;
(gdb) n
32 while(fscanf(fp,"%s",word) != EOF)
(gdb) n
38 result = crypt(word, salt);
(gdb) x/16xw word
0xffffd3fc: 0x31313131 0x00000000 0x00000000 0x00000000
0xffffd40c: 0x00000000 0x71243624 0x4b6c2f68 0x246b4f58
0xffffd41c: 0x24362400 0x6c2f6871 0x6b4f584b 0x4f556824
0xffffd42c: 0x4c4b3051 0x35305759 0x465a654f 0x766b6b4f
(gdb) n
39 if(strcmp(result, hash) == 0) // 비교해서 맞으면
(gdb) p result
$17 = 0x804b170 "$6$qh/lKXOk$SD3Hk0oz4kag5rgWfcl7c7DxMxP1gP/Mdhq8p1fzH3Ucpa2WTAZ/zUTFywgyEDa5NUVKjdhPp4mPFEk.lz/4z."
(gdb) p hash
$18 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
46 count++;
(gdb) n
32 while(fscanf(fp,"%s",word) != EOF)
(gdb) n
38 result = crypt(word, salt);
(gdb) p word
$19 = "11111", '\000' <repeats 14 times>
(gdb) x/16xw word
0xffffd3fc: 0x31313131 0x00000031 0x00000000 0x00000000
0xffffd40c: 0x00000000 0x71243624 0x4b6c2f68 0x246b4f58
0xffffd41c: 0x24362400 0x6c2f6871 0x6b4f584b 0x4f556824
0xffffd42c: 0x4c4b3051 0x35305759 0x465a654f 0x766b6b4f
(gdb) n
39 if(strcmp(result, hash) == 0) // 비교해서 맞으면
(gdb) p result
$20 = 0x804b170 "$6$qh/lKXOk$nDdR6a0lGDN.ogb7RE5JuKLeYWUeQ/piuem78tNPug7mZnTnDuquWxgtL.RaiKGAqfyRGuiUoUJV4aPn6oDzs/"
(gdb) p hash
$21 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
46 count++;
(gdb) n
32 while(fscanf(fp,"%s",word) != EOF)
(gdb) p count
$22 = 6
(gdb) n
38 result = crypt(word, salt);
(gdb) x/16xw word
0xffffd3fc: 0x31313131 0x00003131 0x00000000 0x00000000
0xffffd40c: 0x00000000 0x71243624 0x4b6c2f68 0x246b4f58
0xffffd41c: 0x24362400 0x6c2f6871 0x6b4f584b 0x4f556824
0xffffd42c: 0x4c4b3051 0x35305759 0x465a654f 0x766b6b4f
(gdb) n
39 if(strcmp(result, hash) == 0) // 비교해서 맞으면
(gdb) x/16xw result
0x804b170: 0x71243624 0x4b6c2f68 0x246b4f58 0x514f5568
0x804b180: 0x594c4b30 0x4f353057 0x4f465a65 0x42766b6b
0x804b190: 0x67577249 0x7a78664e 0x4537772e 0x684a3176
0x804b1a0: 0x432e3135 0x5239424b 0x66314d57 0x2e4f6f51
(gdb) p result
$23 = 0x804b170 "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) p hash
$24 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
41 printf(">>> count : %d <<<\n", count);
(gdb) p $eax
$25 = 0
(gdb) n
>>> count : 6 <<<
42 printf("[*] password is: %s\n" , word);
(gdb) p count
$26 = 6
(gdb) n
[*] password is: 111111
43 found = 1;
(gdb) p found
$27 = 0
(gdb) n
44 break;
(gdb) p found
$28 = 1
(gdb) n
49 if(!found) // 비밀번호를 찾지 못했다면
(gdb) n
52 fclose(fp);
(gdb) n
54 return 0;
(gdb) c
Continuing.
[Inferior 1 (process 2067) exited normally]
(gdb) q
실습> gdb 를 이용한 분석
사전 파일인 dict.txt 파일이 없을 경우
fp 변수에 NULL 포인터(0x00000000)가 리턴된다.
1. 컴파일
# gcc -m32 -g -o dictAttack dictAttack.c -DDEBUG -lcrypt
# ./dictAttack
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
>>> count : 6 <<<
[*] password is: 111111
2. 사전 파일 이름 변경
dict.txt 파일을 dict.txt.bak 파일로 이름을 변경한다.
# mv dict.txt dict.txt.bak
dict.txt 파일이 존재하지 않으므로 에러를 출력하고 프로세스를 종료한다.
# ./dictAttack
[-] dict.txt 파일이 없습니다.
3. gdb 분석
# gdb dictAttack
Reading symbols from /root/dictAttack...done.
(gdb) b main
Breakpoint 1 at 0x804860c: file dictAttack.c, line 17.
(gdb) r
Starting program: /root/dictAttack
Breakpoint 1, main () at dictAttack.c:17
17 char hash[] = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51"
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.i686 nss-softokn-freebl-3.79.0-4.el7_9.i686
(gdb) n
19 char salt[] = "$6$qh/lKXOk$";
(gdb) n
20 char word[20] = "\0";
(gdb) n
22 int count = 1;
(gdb) n
23 int found = 0;
(gdb) n
25 FILE *fp = fopen("dict.txt","r");
(gdb) n
26 if(fp == NULL)
(gdb) p sizeof(fp)
$1 = 4
dict 파일이 없으면 NULL 포인터(32bit 이므로 0x00000000) 가 저장된다.
(gdb) x/xw &fp
0xffffd484: 0x00000000
(gdb) n
28 fprintf(stderr, "[-] dict.txt 파일이 없습니다.\n");
(gdb) n
[-] dict.txt 파일이 없습니다.
29 return 1;
(gdb) n
55 }
eax 레지스터에 리턴값이 저장되므로 확인한다.
(gdb) p $eax
$2 = 1
(gdb) c
Continuing.
[Inferior 1 (process 2148) exited with code 01]
(gdb) q
실습> gdb 를 이용한 분석
사전 파일인 dict.txt 파일이 있을 경우
fp 변수에 어떤 주소가 리턴된다.
1. 사전 파일 이름 변경
# mv dict.txt.bak dict.txt
# ./dictAttack
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
>>> count : 6 <<<
[*] password is: 111111
2. gdb 분석
# gdb dictAttack
Reading symbols from /root/dictAttack...done.
(gdb) b main
Breakpoint 1 at 0x804860c: file dictAttack.c, line 17.
(gdb) r
Starting program: /root/dictAttack
Breakpoint 1, main () at dictAttack.c:17
17 char hash[] = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51"
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.i686 nss-softokn-freebl-3.79.0-4.el7_9.i686
(gdb) n
19 char salt[] = "$6$qh/lKXOk$";
(gdb)
20 char word[20] = "\0";
(gdb)
22 int count = 1;
(gdb)
23 int found = 0;
(gdb)
25 FILE *fp = fopen("dict.txt","r");
(gdb)
26 if(fp == NULL)
파일이 존재할 경우 파일 포인터 fp 변수에 어떤 값이 저장된다.
(gdb) x/xw fp
0x804b008: 0xfbad2488
if(fp == NULL) 이 거짓이므로 그 다음 코드인 while문 쪽으로 이동한 것이다.
(gdb) n
32 while(fscanf(fp,"%s",word) != EOF)
(gdb) c
Continuing.
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
>>> count : 6 <<<
[*] password is: 111111
[Inferior 1 (process 2174) exited normally]
(gdb) q
# vi dictAttack2.c
/*
* 파일명: dictAttack2.c
* 프로그램 설명: 사전파일 공격
* 작성자: 리눅스마스터넷
* 컴파일: gcc -g -o dictAttack dictAttack.c -lcrypt
*/
#include <stdio.h>
#include <string.h>
#include <crypt.h>
int main()
{
// /etc/shadow에서 가져와서 각자의 값으로 채운다.
char hash[] = "$6$5WdPl43d$Xzv9gUyXaiA5iT4mDyHLloUt/b9sUsGu1p/3rC0Pi"
"kFteQ8K2vp1x7vJuGS6zOIsT4g824FrZ/wPHeQYR281G0";
char key[] = "$6$5WdPl43d$";
char word[20] = "\0";
char *result;
int count = 1;
int found = 0;
FILE *fp = fopen("dict.txt","r"); // 파일 열기
if(fp == NULL) // 파일이 없다면
{
fprintf(stderr, "[-] dict.txt 파일이 없습니다.\n"); // 에러를 출력하고
return 1; // 프로세스를 종료한다.
}
while(fscanf(fp,"%s",word) != EOF) // dict.txt 파일을 한 줄 읽어서 word에 저장한다.
{
#ifdef DEBUG
printf("Debug: %s\n", word);
#enddef
result = crypt(word, key); // 암호화해서 나온 결과를 result 변수에 저장한다.
if(strcmp(result, hash) == 0) // 저장된 해쉬값과 result가 맞으면
{
found = 1;
printf(">>> count : %d <<<\n", count); // count를 출력한다.
printf("[*] password is: %s\n" , word); // 암호를 출력한다.
break; // while문 탈출
}
count++; // 카운트 증가
}
fclose(fp); // 파일 닫기
if(!found) // 비밀번호를 못찾으면
{
printf(">>> count : %d <<<\n", --count);
printf("[-] password not found!!!\n");
}
return 0;
}
# gcc -o dictAttack2 dictAttack2.c -DDEBUG -lcrypt
# ./dictAttack2
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
Debug: 1111111
Debug: a
Debug: aa
Debug: aaa
>>> count : 10 <<<
[*] password is: aaa
# gcc -o dictAttack2 dictAttack2.c -lcrypt
# ./dictAttack2
>>> count : 10 <<<
[*] password is: aaa
실습> dictAttack.c를 파이썬으로 변경하기
파일명: dictAttack.c -> dictAttack.py
1. 예외처리가 안된 코드
파이썬으로 파일 읽기
# vi fileOpen.py
#!/usr/bin/env python
"""
파일명: fileOpen.py
프로그램 설명: 파이썬으로 파일 읽기(예외처리가 없는 경우)
작성자: 리눅스마스터넷
"""
count = 1
f = open("dict.txt", "rt")
for word in f:
print(f"{count} ", end='')
print(word, end='')
count += 1
f.close()
# mv dict.txt dict.txt.bak
# chmod 755 fileOpen.py
# ./fileOpen.py
Traceback (most recent call last):
File "./fileOpen.py", line 9, in <module>
f = open("dict.txt", "rt")
FileNotFoundError: [Errno 2] No such file or directory: 'dict.txt'
2. 예외처리가 된 코드
파이썬으로 파일 읽기
# vi fileOpen.py
#!/usr/bin/env python
"""
파일명: fileOpen.py
프로그램 설명: 파이썬으로 파일 읽기(예외처리가 있는 경우)
작성자: 리눅스마스터넷
"""
count = 1
try:
f = open("dict.txt", "rt")
for word in f:
print(f"{count} ", end='')
print(word, end='')
count += 1
except FileNotFoundError as e:
print(e)
else:
f.close()
# ./fileOpen.py
[Errno 2] No such file or directory: 'dict.txt'
실습> dictAttack.py 파일 생성하기
1. 소스코드
open ~ close 를 이용한 경우
dictAttack.py 파일을 생성한다.
(cryptProject) [root@localhost ~]# vi dictAttack.py
#!/usr/bin/env python
"""
파일명: dictAttack.py
프로그램 설명: 사전파일 공격
작성자: 리눅스마스터넷
"""
import sys
import crypt
# /etc/shadow에서 가져와서 각자의 값으로 채운다.
# grep passuser /etc/shadow
# passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
hash = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
salt = "$6$qh/lKXOk$"
count = 1
found = 0
DEBUG = 0
try:
fp = open("dict.txt","rt")
for word in fp:
word = word.replace('\n', '') # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
if DEBUG:
print(f"Debug: {word}")
result = crypt.crypt(word, salt)
if result == hash: # 비교해서 맞으면
print(f">>> count : {count} <<<")
print(f"[*] password is: {word}")
found = 1
break
count += 1
except FileNotFoundError as e:
#print(e)
print("[-] dict.txt 파일이 없습니다.\n", file=sys.stderr)
else:
if not found: # 비밀번호를 찾지 못했다면
print("password not found!")
fp.close()
(cryptProject) [root@localhost ~]# chmod 755 dictAttack.py
(cryptProject) [root@localhost ~]# ./dictAttack.py
[-] dict.txt 파일이 없습니다.
(cryptProject) [root@localhost ~]# mv dict.txt.bak dict.txt
(cryptProject) [root@localhost ~]# ./dictAttack.py
>>> count : 6 <<<
[*] password is: 111111
2. 소스코드
with문을 이용한 경우
(cryptProject) [root@localhost ~]# vi dictAttack2.py
#!/usr/bin/env python
"""
파일명: dictAttack2.py
프로그램 설명: 사전파일 공격
작성자: 리눅스마스터넷
"""
import sys
import crypt
# /etc/shadow에서 가져와서 각자의 값으로 채운다.
# grep passuser /etc/shadow
# passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
hash = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
salt = "$6$qh/lKXOk$"
count = 1
found = 0
DEBUG = 0
try:
with open("dict.txt","rt") as fp:
for word in fp:
if DEBUG:
print(f"Debug: {word}", end='')
word = word.replace('\n', '') # 한 줄의 엔터를 제거한다.
result = crypt.crypt(word, salt)
if result == hash: # 비교해서 맞으면
print(f">>> count : {count} <<<")
print(f"[*] password is: {word}")
found = 1
break
count += 1
except FileNotFoundError as e:
#print(e)
print("[-] dict.txt 파일이 없습니다.\n", file=sys.stderr)
else:
if not found: # 비밀번호를 찾지 못했다면
print("password not found!")
(cryptProject) [root@localhost ~]# chmod 755 dictAttack2.py
(cryptProject) [root@localhost ~]# ./dictAttack2.py
>>> count : 6 <<<
[*] password is: 111111
실습> python 디버깅
pdb: gdb 하고 비슷한 텍스트형태의 디버깅 툴
(cryptProject) [root@localhost ~]# python -m pdb dictAttack.py
> /root/dictAttack.py(2)<module>()
-> """
(Pdb) list 1, 100
1 #!/usr/bin/env python
2 -> """
3 파일명: dictAttack.py
4 프로그램 설명: 사전파일 공격
5 작성자: 리눅스마스터넷
6 """
7
8 import sys
9 import crypt
10
11 # /etc/shadow에서 가져와서 각자의 값으로 채운다.
12 # grep passuser /etc/shadow
13 # passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
14 hash = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
15 salt = "$6$qh/lKXOk$"
16 count = 1
17 found = 0
18 DEBUG = 0
19
20 try:
21 fp = open("dict.txt","rt")
22
23 for word in fp:
24 word = word.replace('\n', '') # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
25
26 if DEBUG:
27 print(f"Debug: {word}")
28
29 result = crypt.crypt(word, salt)
30
31 if result == hash: # 비교해서 맞으면
32 print(f">>> count : {count} <<<")
33 print(f"[*] password is: {word}")
34 found = 1
35 break
36 count += 1
37
38 except FileNotFoundError as e:
39 #print(e)
40 print("[-] dict.txt 파일이 없습니다.\n", file=sys.stderr)
41 else:
42 if not found: # 비밀번호를 찾지 못했다면
43 print("password not found!")
44 fp.close()
45
[EOF]
(Pdb)
(Pdb) b 14
Breakpoint 1 at /root/dictAttack.py:14
(Pdb) r
> /root/dictAttack.py(14)<module>()
-> hash = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(Pdb) n
> /root/dictAttack.py(15)<module>()
-> salt = "$6$qh/lKXOk$"
(Pdb) n
> /root/dictAttack.py(16)<module>()
-> count = 1
(Pdb) n
> /root/dictAttack.py(17)<module>()
-> found = 0
(Pdb) n
> /root/dictAttack.py(18)<module>()
-> DEBUG = 0
(Pdb)
> /root/dictAttack.py(20)<module>()
-> try:
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) p salt
'$6$qh/lKXOk$'
(Pdb) p count
1
(Pdb) p found
0
(Pdb) p DEBUG
0
(Pdb) n
> /root/dictAttack.py(21)<module>()
-> fp = open("dict.txt","rt")
(Pdb) n
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb) p fp
<_io.TextIOWrapper name='dict.txt' mode='rt' encoding='UTF-8'>
(Pdb) n
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '') # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'1\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) p word
'1'
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb) n
> /root/dictAttack.py(31)<module>()
-> if result == hash: # 비교해서 맞으면
(Pdb) p result
'$6$qh/lKXOk$ZxAnywAnw6ypFIMpdThNUIel/ijRDX.kn4kHVVVLzyXouhUczi/PmjRCcDo3clbV59CTtKH3/tBC1ZSNnqTJ8/'
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) n
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb) p count
1
(Pdb) n
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb) p count
2
(Pdb) n
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '') # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'11\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) p word
'11'
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb) n
> /root/dictAttack.py(31)<module>()
-> if result == hash: # 비교해서 맞으면
(Pdb) p result
'$6$qh/lKXOk$fJ2AGgP6Pfpmw1qsVRjZyftknF.e3xKFNsAGevfUrh8G5.KaMB2CeYD123mRcoXHjIDPaq5wWZn2.Xs2wWIqP.'
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) n
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb) n
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb) n
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '') # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'111\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) p word
'111'
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb) n
> /root/dictAttack.py(31)<module>()
-> if result == hash: # 비교해서 맞으면
(Pdb) p result
'$6$qh/lKXOk$D9nudUorSMOgfT6jCp3IIAAF.5V3MpiIGepdDcUYBU2NxSREtu.ea34tNuNGbuv.kQ3pEYfxwOs4oyG0nkcVX/'
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) n
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb) n
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb) p count
4
(Pdb) n
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '') # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'1111\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb)
> /root/dictAttack.py(31)<module>()
-> if result == hash: # 비교해서 맞으면
(Pdb)
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb)
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb)
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '') # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'11111\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb)
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb)
> /root/dictAttack.py(31)<module>()
-> if result == hash: # 비교해서 맞으면
(Pdb)
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb)
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb)
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '') # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'111111\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) p word
'111111'
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb) n
> /root/dictAttack.py(31)<module>()
-> if result == hash: # 비교해서 맞으면
(Pdb) p result
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) n
> /root/dictAttack.py(32)<module>()
-> print(f">>> count : {count} <<<")
(Pdb) n
>>> count : 6 <<<
> /root/dictAttack.py(33)<module>()
-> print(f"[*] password is: {word}")
(Pdb) n
[*] password is: 111111
> /root/dictAttack.py(34)<module>()
-> found = 1
(Pdb) p found
0
(Pdb) n
> /root/dictAttack.py(35)<module>()
-> break
(Pdb) p found
1
(Pdb) n
> /root/dictAttack.py(42)<module>()
-> if not found: # 비밀번호를 찾지 못했다면
(Pdb) n
> /root/dictAttack.py(44)<module>()
-> fp.close()
(Pdb) c
The program finished and will be restarted
> /root/dictAttack.py(2)<module>()
-> """
(Pdb) q
실습> CentOS 7에서 john the ripper를 설치하기
시스템에 비밀번호 점검하기
- 관리자가 사용하면 시스템에 취약한 비밀번호를 점검할 수 있는 점검툴로 활용할 수 있다.
소스코드로 되어있는 패키지를 설치하는 방법
압축해제 > 디렉토리 이동 > 환경설정(./configure) > 컴파일(make) > 설치(make install)
# yum -y install wget
# wget --no-check-certificate https://www.openwall.com/john/k/john-1.9.0.tar.gz
# tar xzf john-1.9.0.tar.gz
# cd john-1.9.0/src/
# make linux-x86-64
# cd ../run/
# file john
# ./john
# ./john --test
# useradd user1
# useradd user2
# useradd passuser
# passwd --stdin user1 <-- 비번을 111111 으로 설정한다.
# passwd --stdin user2 <-- 비번을 111111 으로 설정한다.
# passwd --stdin passuser <-- 비번을 222222 으로 설정한다.
# ./unshadow /etc/passwd /etc/shadow > LinuxPasswd.txt
# ./john LinuxPasswd.txt
강력하게 설정한 비밀번호를 크랙이 쉽지 않고 쉽게 설정한 비밀번호를 아래처럼 금방 크랙이 된다.
# ./john LinuxPasswd.txt
Loaded 5 password hashes with 5 different salts (crypt, generic crypt(3) [?/64])
Press 'q' or Ctrl-C to abort, almost any other key for status
111111 (user2)
111111 (passuser)
222222 (user1)
^C
3g 0:00:02:11 19% 2/3 0.02277g/s 259.9p/s 507.2c/s 507.2C/s wolverine2..cthulhu2
Use the "--show" option to display all of the cracked passwords reliably
Session aborted
# ./john --show LinuxPasswd.txt
user1:222222:1001:1001::/home/user1:/bin/bash
user2:111111:1002:1002::/home/user2:/bin/bash
passuser:111111:1006:1006::/home/passuser:/bin/bash
3 password hashes cracked, 2 left
# cat john.pot
$6$FX0Croyq$/J/W3tQh4kan0XPXp4.VkVtdlZdkrTOic6rhzJS98pT9gT2h2EDec7rq4ysFKvsXx5x2nsJCim0nEUvANHtKx0:111111
$6$6PEqsDr1$UVEU66F6ONnwQXs7326ZRp7P8XgK5vJbFu7N5RswaHq8JHsE/ljpgHGScTbp0sR0UclcPxEF5MdwyE04lDBLC1:111111
$6$hqoQGAew$aHQbqsNIjIzsEMXK4xDOfvYfNHW7HVawavPteTcktoWwHbSNiIjoI0ol3SfkMrJkhMHUdupsdSHcGMK36YqRb1:222222
###########
## SELinux
###########
SELinux 를 사용하면 주체(파일, 디바이스...)에 보안 레이블을 하나 추가된다.
ex) 여행가방에 라벨
보안 레이블 확인하는 옵션
-Z --context
실습> SELinux 관련 패키지 설치
[root@webhacking ~]# yum search seinfo
[root@webhacking ~]# yum install setools-console -y
[root@webhacking ~]# yum -y install policycoreutils-python
실습> 보안 레이블 확인
1. 파일 확인
ls -Z 옵션을 이용해서 확인한다.
[root@webhacking ~]# mkdir selinuxtest; cd selinuxtest
[root@webhacking selinuxtest]# touch testfile
[root@webhacking selinuxtest]# mkdir testdir
[root@webhacking selinuxtest]# ls -Z
drwxr-xr-x. root root unconfined_u:object_r:admin_home_t:s0 testdir
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 testfile
[root@webhacking selinuxtest]# ls --context
drwxr-xr-x. root root unconfined_u:object_r:admin_home_t:s0 testdir
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 testfile
2. 프로세스 확인
ps -Z 옵션을 이용해서 확인한다.
# ps -Z
# ps -efZ
# ps auxZ
[root@webhacking selinuxtest]# seinfo -u
[root@webhacking selinuxtest]# seinfo -r
[root@webhacking selinuxtest]# seinfo -t <-- 이 부분이 중요하다!
[root@webhacking selinuxtest]# seinfo -robject_r -x
[root@webhacking selinuxtest]# pwd
/root/selinuxtest
[root@webhacking selinuxtest]# ls -Z
drwxr-xr-x. root root unconfined_u:object_r:admin_home_t:s0 testdir
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 testfile
[root@webhacking selinuxtest]# seinfo -tadmin_home_t -x
admin_home_t
file_type
mountpoint
non_auth_file_type
non_security_file_type
polymember
polyparent
sandbox_typeattr_4
sandbox_typeattr_3
sandbox_typeattr_2
sandbox_typeattr_1
[root@webhacking selinuxtest]# systemctl start httpd
[root@webhacking selinuxtest]# ps auxZ | grep httpd
system_u:system_r:httpd_t:s0 root 917 0.0 1.4 369016 14316 ? Ss 09:57 0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 1331 0.0 0.7 369016 6988 ? S 09:57 0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 1332 0.0 0.7 369016 6988 ? S 09:57 0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 1333 0.0 0.7 369016 6988 ? S 09:57 0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 1334 0.0 0.7 369016 6988 ? S 09:57 0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 1335 0.0 0.7 369016 6988 ? S 09:57 0:00 /usr/sbin/httpd -DFOREGROUND
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 root 1893 0.0 0.1 116972 1012 pts/0 R+ 11:11 0:00 grep --color=auto httpd
[root@webhacking selinuxtest]# seinfo -thttpd_t -x
httpd_t
nsswitch_domain
can_change_object_identity
corenet_unlabeled_type
domain
kernel_system_state_reader
netlabel_peer_type
daemon
syslog_client_type
pcmcia_typeattr_7
pcmcia_typeattr_6
pcmcia_typeattr_5
pcmcia_typeattr_4
pcmcia_typeattr_3
pcmcia_typeattr_2
pcmcia_typeattr_1
sepgsql_client_type
Aliases
phpfpm_t
실습> 웹 파일의 보안 레이블
[root@webhacking selinuxtest]# systemctl start httpd
[root@webhacking selinuxtest]# echo "SELinux TEST" > /var/www/html/setest.html
[root@webhacking selinuxtest]# cat /var/www/html/setest.html
SELinux TEST
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# ls -dZ /var/www/html/
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html/
[root@webhacking selinuxtest]# ps -ZC httpd
LABEL PID TTY TIME CMD
system_u:system_r:httpd_t:s0 917 ? 00:00:00 httpd
system_u:system_r:httpd_t:s0 1331 ? 00:00:00 httpd
system_u:system_r:httpd_t:s0 1332 ? 00:00:00 httpd
system_u:system_r:httpd_t:s0 1333 ? 00:00:00 httpd
system_u:system_r:httpd_t:s0 1334 ? 00:00:00 httpd
system_u:system_r:httpd_t:s0 1335 ? 00:00:00 httpd
httpd가 httpd_sys_content_t 의 권한이 있는 경우 (출력된다.)
[root@webhacking selinuxtest]# sesearch -A -t httpd_sys_content_t -s httpd_t -d
Found 4 semantic av rules:
allow httpd_t httpd_sys_content_t : lnk_file { read getattr } ;
allow httpd_t httpd_sys_content_t : dir { ioctl read getattr lock search open } ;
allow httpd_t httpd_sys_content_t : file { ioctl read getattr lock map open } ;
allow httpd_t httpd_sys_content_t : dir { ioctl read write getattr lock add_name remove_name search open } ;
httpd가 admin_home_t 의 권한이 없는 경우 (출력이 안된다.)
[root@webhacking selinuxtest]# sesearch -A -t admin_home_t -s httpd_t -d
<-- 출력이 없음.
퍼미션을 확인한다.(644)
[root@webhacking selinuxtest]# ll /var/www/html/setest.html
-rw-r--r--. 1 root root 13 4월 5 11:17 /var/www/html/setest.html
[root@webhacking selinuxtest]# yum -y install lynx
웹페이지 접근
첫 번째 보안 레이블 확인: httpd가 httpd_sys_content_t 에 대해서 read 권한이 있으므로 접근 허용
두 번째 파일 퍼미션 확인: 644이므로 읽기 권한이 있으므로 접근 허용
SELinux의 타입에 대한 도메인의 접근 권한이 있기 때문에 두 번째 파일 퍼미션을 확인한 것이고
이것도 접근할 수 있으므로 apache 웹서버가 setest.html 파일의 내용을 읽어서 클라이언트에서 전송한 것이다.
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
SELinux TEST
SELinux Security Context 변경
chcon 사용법
Usage: chcon [OPTION]... CONTEXT FILE...
or: chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...
or: chcon [OPTION]... --reference=RFILE FILE...
Security Context 를 httpd_sys_content_t -> admin_home_t로 변경한다.
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# chcon -t admin_home_t /var/www/html/setest.html
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 /var/www/html/setest.html
Security Context 를 변경한 후 접근할려고 했더니 Forbidden 이 나오면서 접근할 수 없다.
이유: 타입(admin_home_t)에 대한 도메인(httpd_t)의 허용된 정책이 없기 때문이다.
[root@webhacking selinuxtest]# sesearch -A -t admin_home_t -s httpd_t -d
[root@webhacking selinuxtest]#
[root@webhacking selinuxtest]# getenforce
Enforcing
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
Forbidden
You don't have permission to access /setest.html on this server.
SELinux Security Context 변경
변경하는 방법 첫 번째 (자동 변경):
- restorecon 명령어를 사용하는 방법
- 자신의 디렉터리의 타입으로 그대로 자동으로 변경한다.
변경하는 방법 두 번째 (수동 변경):
- chcon 명령어를 사용하는 방법
- 자신이 직접 타입을 명시해서 변경한다.
변경하는 방법 첫 번째 (자동 변경): restorecon 을 이용한다.
[root@webhacking selinuxtest]# restorecon /var/www/html/setest.html
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# sesearch -A -t httpd_sys_content_t -s httpd_t -d
Found 4 semantic av rules:
allow httpd_t httpd_sys_content_t : lnk_file { read getattr } ;
allow httpd_t httpd_sys_content_t : dir { ioctl read getattr lock search open } ;
allow httpd_t httpd_sys_content_t : file { ioctl read getattr lock map open } ;
allow httpd_t httpd_sys_content_t : dir { ioctl read write getattr lock add_name remove_name search open } ;
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
SELinux TEST
변경하는 방법 두 번째 (수동 변경): chcon 을 이용한다.
[root@webhacking selinuxtest]# chcon -t admin_home_t /var/www/html/setest.html
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
Forbidden
You don't have permission to access /setest.html on this server.
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# chcon -t httpd_sys_content_t /var/www/html/setest.html
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
SELinux TEST
실습> PAM
pam_succeed_if.so
- if문으로 비교할 때 사용하는 모듈
반환 값
- 성공: 비교가 맞으면
- 실패: 비교가 맞지 않으면
pam_rootok.so
- root인지 아닌지 확인하는 모듈
반환값
- 성공: root 이면
- 실패: root가 아니면
-
pam_wheel.so
- wheel 그룹에 포함된 사용자인지 확인하는 모듈
반환값
- 성공: wheel 그룹에 포함되어 있으면
- 실패: wheel 그룹에 포함되어 있지 않으면
sufficient
- 인증 결과가 맞으면 성공을 반환하고 즉시 인증 성공 (다음 모듈을 실행하지 않음)
- 인증 결과가 틀리면 실패를 반환하고 인증에 영향을 미치지 않고 다음 모듈을 실행
requisite
- 인증 결과가 맞으면 성공을 반환하고 인증에 영향을 미치지 않고 다음 모듈을 실행
- 인증 결과가 틀리면 실패를 반환하고 즉 시 인증 실패 (다음 모듈을 실행하지 않음)
required
- 인증 결과가 맞으면 성공을 반환하고 다음 모듈을 실행하고 성공을 해야 최종 인증 결과는 성공
- 인증 결과가 틀리면 실패를 반환하고 다음 모듈을 실행하고 최종 인증 결과는 실패
# useradd test1
# useradd test2
# vi /etc/pam.d/su
1 #%PAM-1.0
2 auth sufficient pam_rootok.so
3 # Uncomment the following line to implicitly trust users in the "wheel" group.
4 #auth sufficient pam_wheel.so trust use_uid
5 # Uncomment the following line to require a user to be in the "wheel" group.
6 #auth required pam_wheel.so use_uid
7 auth substack system-auth
8 auth include postlogin
9 account sufficient pam_succeed_if.so uid = 0 use_uid quiet
10 account include system-auth
:
:(생략)
# su - test1
$
$ su -
암호: <-- 암호를 입력한다.
# exit
$ exit
#
# usermod -G wheel test1
# grep wheel /etc/group
wheel:x:10:test1
# vi /etc/pam.d/su
1 #%PAM-1.0
2 auth sufficient pam_rootok.so
3 # Uncomment the following line to implicitly trust users in the "wheel" group.
4 #auth sufficient pam_wheel.so trust use_uid
5 # Uncomment the following line to require a user to be in the "wheel" group.
6 auth required pam_wheel.so use_uid
7 auth substack system-auth
8 auth include postlogin
:
:(생략)
wheel 그룹에 포함된 사용자
# su - test1
마지막 로그인: 수 4월 5 12:12:48 KST 2023 일시 pts/1
$ <-- 2번 라인에 의해서 root 이므로 성공을 리턴했으므로 일반유저($)로 변경되었다.
su - 를 하면 /etc/pam.d/su 파일을 검사한다.
use_uid: 현재 실행하는 사용자를 말한다.
2 auth sufficient pam_rootok.so <-- 인증이 실패되었지만 최종 인증하고는 무관하고 다음 모듈을 실행
6 auth required pam_wheel.so use_uid <-- wheel 그룹에 포함되어 있으므로 성공을 반환한다.
7 auth substack system-auth <-- root 로 로그인하는 화면이 출력되고 이 부분에 비밀번호가 맞으면 성공
$ id
uid=1001(test1) gid=1001(test1) groups=1001(test1),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$ su -
암호: <-- 비밀번호를 정확히 입력한다.
마지막 로그인: 수 4월 5 12:14:50 KST 2023 일시 pts/1
# exit
$ exit
wheel 그룹에 포함되지 않은 사용자
# su - test2
$ id
uid=1002(test2) gid=1002(test2) groups=1002(test2) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
su - 를 하면 /etc/pam.d/su 파일을 검사한다.
use_uid: 현재 실행하는 사용자를 말한다.
2 auth sufficient pam_rootok.so <-- 인증이 실패되었지만 최종 인증하고는 무관하고 다음 모듈을 실행
6 auth required pam_wheel.so use_uid <-- wheel 그룹에 포함되어 있지 않으므로 실패을 반환한다.
7 auth substack system-auth <-- root 로 로그인하는 화면이 출력되고 이 부분에 비밀번호가 맞으면 성공
$ su -
암호: <-- 비밀번호를 정확하게 입력한다.
su: 권한 부여 거부
$ exit
# vi /etc/pam.d/su
1 #%PAM-1.0
2 auth sufficient pam_rootok.so
3 auth sufficient pam_succeed_if.so user = test2 quiet <-- use_uid 가 없을 경우
:
:(생략)
[root@webhacking ~]# su - test1
[test1@webhacking ~]$ id
uid=1001(test1) gid=1001(test1) groups=1001(test1),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
2 auth sufficient pam_rootok.so <-- 인증이 실패되었지만 최종 인증하고는 무관하고 다음 모듈을 실행
3 auth sufficient pam_succeed_if.so user = test2 quiet <-- 변경하려는 사용자가 test2와 같으면 성공을 리턴하므로 즉시 성공
7 auth substack system-auth
[test1@webhacking ~]$ su test2 <-- user = test2 와 같기 때문에 인증이 즉시 성공되었다.
[test2@webhacking test1]$ exit
[test1@webhacking ~]$ su - test1
암호: <-- system-auth 가 실행된 것이다.
[test2@webhacking test1]$ exit
[test1@webhacking ~]$ exit
# vi /etc/pam.d/su
1 #%PAM-1.0
2 auth sufficient pam_rootok.so
3 auth sufficient pam_succeed_if.so user = test2 use_uid quiet <-- use_uid 가 있을 경우
[root@webhacking ~]# su - test2
su 명령어를 실행하는 사용자가 test2와 같으면 인증에 즉시 성공을 리턴한다.
2 auth sufficient pam_rootok.so <-- 인증이 실패되었지만 최종 인증하고는 무관하고 다음 모듈을 실행
3 auth sufficient pam_succeed_if.so user = test2 use_uid quiet <-- use_uid 가 있을 경우(즉시 성공을 리턴한다.)
[test2@webhacking ~]$ su test1
[test1@webhacking test2]$