파일 업로드 취약점
1. 개요
1.1 파일 업로드 취약점
파일 업로드 취약점은 파일 첨부를 할 수 있는 게시판에 일반적으로 허용된 파일 (이미지 파일, 문서파일) 이외의 악의적인 스크립트가 포함된 소스파일 (.jsp , .php, .asp 등.. ) 을 첨부 할 수 있게 되면 공격자는 악성 스크립트 업로드 후 서버상에서 스크립트를 실행시켜 쉘 획득, 서버변조 등 웹서버를 장악할 수 있다.
2. 점검 방법
2.1 STEP 1
파일 첨부 기능이 있는 페이지를 찾는다.
ex ) 게시판, 자료실 등
2.2 STEP 2
첨부기능이 존재하는 경우 지정된 확장자 이외의 파일을 업로드 할 수 있는지 확인한다.
ASP , ASPX , JSP , PHP , CER , CDX , ASA , PHP3 , WAR 등
클라이언트 소스에서 방어코드가 있을 경우, 프록시 등을 이용해 우회해서 확인
2.3 STEP 3
파일 업로드가 가능할 경우 업로드 된 파일에 접근이 가능한지, 실행이 가능한 지 확인
접근이 가능한 경우 피싱페이지로 활용될 수 있고, 실행까지 가능한 경우 시스템의 제어권한을 획득 가능하다.
3. 조치 권고안
3.1 일반 권고안
[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 로 첨부
3.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 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) |
3.3 시스템 설정 권고안 ( IIS )
업로드 된 파일이 존재하는 디렉토리에서는 에플리케이션이 실행될 수 없도록 실행권하능 제거 한다.
[1] 인터넷정보 서비스 à 업로드 폴더 à 속성 à 등록정보 / 디렉토리 à 실행권한 없음 ( 2003 경우 )
[2] 인터넷 정보 서비스 à 업로드 폴더 선택 à 처리기 매핑 à 사용권한 편집 à 스크립트 해제 ( 2003 이상 )
3.4 시스템 설정 권고안 ( Apache )
[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 지시자를 사용하면 차단 설정한 확장자의 파일 다운로드는 거부된다.
4. 별첨 1 ( 웹쉘 키워드 정리 )
4.1 자주 사용되는 웹쉘 문구의 정규식 표현의 예
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) |