파일 업로드 취약점은 파일 첨부를 할 수 있는 게시판에 일반적으로 허용된 파일 (이미지 파일, 문서파일) 이외의
악의적인 스크립트가 포함된 소스파일 (.jsp , .php, .asp 등.. ) 을 첨부 할 수 있게 되면 공격자는 악성 스크립트 업로드 후 서버상에서 스크립트를 실행시켜 쉘 획득, 서버변조 등 웹서버를 장악할 수 있다.
파일 첨부 기능이 있는 페이지를 찾는다.
ex ) 게시판, 자료실 등
첨부기능이 존재하는 경우 지정된 확장자 이외의 파일을 업로드 할 수 있는지 확인한다.
ASP , ASPX , JSP , PHP , CER , CDX
, ASA , PHP3 , WAR 등
클라이언트 소스에서 방어코드가 있을 경우, 프록시 등을
이용해 우회해서 확인
파일 업로드가 가능할 경우 업로드 된 파일에 접근이
가능한지, 실행이 가능한 지 확인
접근이 가능한 경우 피싱페이지로 활용될 수 있고, 실행까지 가능한 경우 시스템의 제어권한을 획득 가능하다.
[1] 서비스를 위해
허용하고 있는 파일의 확장자 외에는 업로드가 불가능 하도록 해야 하며 스크립트 파일의 업로드 및 실행을 금지 시킨다. ( 파일 업로드 기능이 있는 모든 곳에 적용 )
[2] 사용자가 파일을
업로드 할 때 확장자의 적합성 여부를 검사하는 루틴은 서버측에서 구현
[3] 파일명 및 확장자
검증을 우회하는 것을 방지하기 위하여 대소문자를 구분하지 않고 문자열을 비교해야 하며 특수문자가 포함되어 있을 경우 업로드를 금지시킨다.
※ 우회기법의 예 : “shell.Asp”,
“shell. aSp”, “shell.asP”, “shell.asp%00.jpg”, “shell.asp\00.jpg”
[4] 다운로드 스크립트를 구성할 때, 파일명을 Parameter 로 받는 것 보다 파일이름은 데이터베이스에
저장하고 해당 index만을 Parameter로 받아서 사용해야
한다.
[5] 아주 작거나 큰 파일을 처리하는 로직을 포함해야 하고, 임시 디렉토리에서 업로드 된 파일을 지우거나 다른 곳으로 이동시켜야 한다.
[6] 웹 서버 엔진 설정 시 업로드 된 디렉토리의 Server Side Script 언어의 실행 권한을 제거하고 업로드 된 파일명,
확장자 난수화 하여 변경
[7] 업로드 된 파일을 루트 디렉토리와 동일한 수준에 존재하는
다른 디렉토리에 저장하거나, URL 요청에 의해서 직접 접근 (http://[hostname]/[upload_dir]/[uploadfile name]) 이
불가능한 곳에 저장하며, 상위 폴더 경로 이동을 제한한다.
[8] 업로드 파일의 컨텐츠는 문자 검색을 하여 저장을 제한한다. (request
, eval , exec , cmd등)
※ 웹쉘에 자주 이용되는 문구는 문서 아래 별첨 1 로 첨부
[9] 상용 , 오픈소스 에디터의 경우 정기적으로 업데이트 하여 사용한다.
※ 에디터 관련 문서는 문서 아래 별첨2 로 첨부
※ 아래
코드는 예로서 제시하는 것으로 구현시 다를 수 있음. 충분히 테스트 한 후 적용할 것을 권고함
■ ASP
<%
Set Up =
Server.CreateObject("SiteGalaxyUpload.Form")
Path1 = server.mappath(".") &
"\upload\"
Fname = Up("file1")
if Fname <> "" then' 파일 첨부가 되었으면
if Up("file1").Size > 10240 then' 용량 제한
Response.Write "용량 초과"
Response.End
end if
if Up("file1").MimeType <> "image"
then' 이미지만 업로드 허용
Response.Write "이미지 파일이 아닙니다."
Response.End
end if
Filename=Mid(Fname,InstrRev(Fname,"\")+1)'파일이름부분 추출
' 중복시에 파일이름부분을
변경하기 위해 분리를 한다
Farry=split(Filename,".")'.을 기준으로 분리
preFname=Farry(0)'파일이름 앞부분
extFname=Farry(1)'파일의 확장자
' 저장할 전체 path를 만든다, 파일이름을 구한다
Path2 = Path1 & Filename
saveFname=preFname & "." & extFname
Set fso = CreateObject("Scripting.FileSystemObject")
countNo = 0' 파일 중복될경우 셋팅 값
fExist=0' 같은 이름의 파일 존재 체크
Do until fExist = 1
If(fso.FileExists(Path2)) Then
countNo = countNo + 1
Path2 = Path1 & preFname & countNo & "."
& extFname
saveFname=preFname & countNo & "." &
extFname
else
fExist=1
End If
Loop
Up("file1").SaveAs(Path2)
response.write(saveFname & " 저장완료")
else
response.write("Error")
end if
Set Up = nothing
%>
|
■ PHP
<?php
$uploaddir = '/var/www/uploads/';
//파일 사이즈가 0byte 보다 작거나 최대 업로드 사이즈보다 크면 업로드를 금지 시킨다.
if($_FILES['userfile']['name'])
if($_FILES['userfile']['size'] <=
0) // 최대 업로드 사이즈
체크 삽입
print "파일 업로드 에러";
exit;
//파일 이름의 특수문자가
있을 경우 업로드를 금지 시킨다.
if
(eregi("[^a-z0-9\._\-]",$_FILES['userfile']['name']))
print "파일 이름의 특수문자 체크";
exit;
//파일 확장자중 업로드를
허용할 확장자를 정의한다.
$full_filename = explode(".",
$_FILES['userfile']['name']);
$extension = $full_filename[sizeof($full_filename)-1];
/* PHP의 경우 확장자 체크를
할 때 strcmp(확장자,"php3"); 로
체크를 하게 되면
pHp3 이나 phP3는 구별을 하지 못하게 되므로 strcasecmp처럼 대소문자
구별을 하지
않고 비교하는 함수를 사용한다. 또한 .을 기준으로 하여 확장자가 하나로 간주하고
프로그램을 할 경우
file.zip.php3 이라고 올린다면 zip파일로 인식하고 그냥 첨부가
되므로 아래와 같이 제일 끝에 존재하는 확장자를 기준으로 점검하도록 한다. */
$extension= strtolower($extension);
if (!( ereg($extension","hwp") ||
ereg($extension","pdf") ||
ereg($extension","jpg")) )
print "업로드 금지 파일 입니다";
exit;
$uploadfile = $uploaddir. $_FILES['userfile']['name'];
if (move_uploaded_file($_FILES['userfile']['tmp_name'],
$uploadfile))
print "파일이 존재하고, 성공적으로 업로드 되었습니다.";
print_r($_FILES);
else
print "파일 업로드 공격의 가능성이 있습니다! 디버깅 정보입니다:\n";
print_r($_FILES);
?>
|
■ JSP
<%@ page contentType="text/html;charset=euc-kr"
%>
<%@ page
import="com.oreilly.servlet.MultipartRequest,com.oreilly.servlet.multipart.
DefaultFileRenamePolicy, java.util.*"%>
<%
String
savePath="/var/www/uploads"; // 업로드 디렉토리
int sizeLimit = 5 * 1024 * 1024 ; // 업로드 파일 사이즈 제한
try
MultipartRequest multi=new MultipartRequest(request, savePath,
sizeLimit, "euc-kr", new DefaultFileRenamePolicy());
Enumeration formNames=multi.getFileNames(); // 폼의 이름 반환
String formName=(String)formNames.nextElement();
String fileName=multi.getFilesystemName(formName); // 파일의 이름 얻기
String file_ext = fileName.substring(fileName.lastIndexOf('.')
+ 1);
if(!( file_ext.equalsIgnoreCase("hwp") ||
file_ext.equalsIgnoreCase("pdf") ||
file_ext.equalsIgnoreCase("jpg")) )
out.print("업로드 금지 파일");
if(fileName == null)
out.print("파일 업로드 실패");
else
fileName=new
String(fileName.getBytes("8859_1"),"euc-kr"); // 한글인코딩
out.print("File Name
: " + fileName);
catch(Exception e)
|
업로드
된 파일이 존재하는 디렉토리에서는 에플리케이션이 실행될 수 없도록 실행권하능 제거 한다.
[1] 인터넷정보 서비스
à 업로드 폴더 à 속성 à 등록정보 / 디렉토리
à 실행권한 없음 (
2003 경우 )
[2] 인터넷 정보
서비스 à 업로드 폴더 선택 à 처리기 매핑 à 사용권한 편집 à 스크립트 해제 (
2003 이상 )
[1] Apache 설정
파일인 httpd.conf 에 해당 디렉토리에 대한 문서 타입을 컨트롤
<Directory "업로드 디렉토리 경로
">
AllowOverride
FileInfo (또는 All)….
…
<Directory>
|
[2]
파일 업로드 디렉토리에 .htaccess 파일을 만들고 다음과 같이 AddType 지시자를 이용, 현재 서버에서 운영되는 Server Side Script 확장자를 text/html 로 MIME Type을 재조정 하여 업로드 된 Server Side Script가
실행 되지 않도록 설정 또는 FileMatch 지시자를 이용하여, *.ph,
*.inc, *lib 등의 Server Side Script 파일에 대해서 직접 URL 호출을 금지
<.htaccess>
<FilesMatch "\.(ph|inc|lib)">
Order allow, deny
Deny from all
</FilesMatch>
AddType text/html .html .htm .php .php3 .php4 .phtml .phps .in
.cgi .pl .shtml .jsp
|
※ 주의 사항
[1] 파일이 업로드
되는 디텍토리에 운영상 필요한 Server Side Script가 존재하는지 확인한다.
[2] 파일 다운로드 프로그램을 사용하지 않고 직접 URL 호출을 통해 파일을 다운받는 경우 FileMatch 지시자를 사용하면 차단 설정한 확장자의 파일 다운로드는
거부된다.
1. [^a-zA-Z_]eval *\(.*_POST *\[.*\]
2. ` *(\$cmd|\$pwd|\$command|\$exec|ps
-aux|\$cmdline|\$Mcmd) *`
3.
CreateObject\("w.*?"(?:&|\+)"[script"&+]+\.("(?:&|\+)")?s.*?"(?:&|\+)"[hel"&+]+l"\)
4.
Response\.write\("<option>(oracle\sodbc|microsoft\saccess|mysql
\sodbc|sql\sserver).*?</option>"\)
5. exec\s+(?:master)?(?:\.)?(?:dbo)?(?:\.)?xp_cmdshell\s+
6. Set\s[\w=\s]+New\s\w+:\w+\.(DelFile|EditFile|CopyFile|
MoveFile|DelFolder|CopyFolder|MoveFolder
|NewFolder)\(\w+\)
7.
javascript:FullForm\("[\w&\s.,"()\\]+(DownFile|EditFile|DelFile|CopyFile|MoveFile)""\)
8. CreateObject\("s.*?"(?:&|\+)"[ripting"&+]+\.("(?:&|\+)")?F.*?
("(?:&|\+)")[ilesystemobjec"&+]+t"\)
9. ACTION=["']{1}\?[\w\d=&]+<%String\s[\w\d]+=config
\.getServletContext\(\)\.getRealPath\("/"\)
10. <%=\s*"\\\\\"[\s&\w]+\.ComputerName
[&\s]+"\\"[&\s\w]+\.UserName\s+%>
11. SELECT\sCount\(\*\)\sFROM\sMASTER\.DBO\.SYSOBJECTS
\sWHERE\sXTYPE='X'\sAND\sNAME='XP_(CMDSHELL|SERVICECONTROL|REGWRITE)
12. (?<=m[y|s]sql_query\()["']+(SHOW)?\s?CREATE TABLE [`'".
]*(tmp_file|temp_r57_table|r57_temp_table|xploit|\$_POST|\$tab)
13. value=<\?[\w\s]*?base64_decode\(\$\w+\)\s\?>
14. exec\(.*?basename\(\$\w+\).*?/bin/sh',\s\$\w+,\s\$\w+
15. find[\s/]+(?:-user|-type)[-\sa-z]*(-perm|-type)\s(?:777|d)
|