본문 바로가기

보안/시큐어코딩

[시큐어코딩실습] 파일 업로드 취약점제거




BoardController.java

view.jsp




파일 업로드/다운로드 취약점을 제거하기 위한 코딩 기법

 

파일 업로드 취약점 제거를 위해 


1. 업로드된 파일 저장시 외부에서 접근 불가능한 경로에 파일 저장한다. 

    웹서버가 있는 DMZ 보다 내부망의 파일서버에 저장하는것이 안전하다.

2. 업로드된 파일명과  실제 저장되는 파일이름(난수발생을 이용해 유추가 어려운 파일명할당) 

    을 다르게 한다.

3. 업로드되는 파일에 대해서는 실행권한을 제거한다.

4. 해당 파일을 다운로드하는 전용 모듈을 작성한다.


LAB에서는 파일업로드가 가능한 글쓰기 게시판의 첨부파일에 대해 


1. 이미지 파일만 업로드 하도록 허용한다.(확장자를 체크하여 jpg 이거나 png 파일만 업로드 

    허용한다. 이 필터는 충분히 우회가 가능하므로 신뢰할 수 는 없다.)

2. 파일이 저장되는 경로를 /WEB-INF/files/ 아래서 지정한다. 

    이 경로는 외부에서 직접 접근이 불가능하다. (서버 내부에 구현된 서비스 컴포넌트를 통해

    서만 사용 가능하다)

3. 저장되는 파일명은 난수발생기를 이용하여 발생된 "난수+시간스템프"을 사용한다. 

    DB에 실제 저장되는 파일명과 요청되는 파일명을 저장한다. 

    이 경우 ../../ 과 같은 직접객체참조 공격의 취약점을 제거할 수 도 있다.


파일 업로드 기능이 있는 글쓰기 요청을 처리하는 BoardController.java 소스를 참조한다.  


 

pwnshell.zip

==> write.do 요청처리  코드


write.do.txt


 @RequestMapping(value="/write.do", method=RequestMethod.POST)
 public String boardWriteProc(@ModelAttribute("BoardModel") BoardModel 

     boardModel, MultipartHttpServletRequest request, HttpSession session){


  // 외부에서 접근 가능하지 않은 경로에 파일 업로드
  String uploadPath = session.getServletContext().getRealPath("/")+
                       "WEB-INF/files/";
  System.out.println("uploadPath: "+uploadPath);
  
  MultipartFile file = request.getFile("file");
  
  // 파일의 사이즈가 10M 보다 작은 경우만 파일 업로드 허용

  // 파일의 타입과 파일 확장자를 검사해서 허용된 파일만 업로드 한다.
  if ( file != null && ! "".equals(file.getOriginalFilename())
    && file.getSize() <= 10240000 

    && file.getContentType().contains("image")

    && file.getOriginalFilename().toLowerCase().endsWith(".jpg") {

   
      

       // 실제저장할때 사용할 파일명 생성

      

       String savedFileName = null;

       File uploadFile = null;

       do { 

           savedFileName=UUID.randomUUID().toString();
           uploadFile=new File(uploadPath+ savedFileName);
       }while(uploadFile.exist());

 

      // 파일 전송

       try {
          file.transferTo(uploadFile);
       } catch (Exception e) {
          System.out.println("upload error");
       }
       

       // DB에 업로드파일명과 저장된 파일명 기록
       boardModel.setFileName(fileName);
       boardModel.setSavedFileName(savedFileName);
      }
  }

  String content =  boardModel.getContent().replaceAll("\r\n", "<br />");  
  boardModel.setContent(content);

  service.writeArticle(boardModel);  
  
  return "redirect:list.do";
 }



==> get_image.do 요청 처리 코드

get_image.do.txt


 

파일 다운로드시 외부에서 직접적으로 파일을 호출하지 못하도록한다.

파일 다운로드를 처리하는 별도의 컴포넌트를 통해 파일 다운로드시에 발생 우려가 있는 취약점들을 점검하고,안전한 파일을 클라이언트에게 전달하도록 코드를 작성한다.


@RequestMapping("/get_image.do")
 public void getImage(HttpServletRequest request, HttpSession session,  HttpServletResponse response){

  
  int idx=TestUtil.getInt(request.getParameter("idx"));


  // 읽어본 게시물인지 확인
  if ( session.getAttribute("idx") ==  null  ||
      (Integer)session.getAttribute("idx") != idx  ) {
   return;
  }


  // 저장된 파일명을 읽어오는 작업이 필요
  BoardModel board = service.getOneArticle(idx);

  // 해당게시물이 없으면 파일다운로드 처리하지 않고 종료

  if ( board == null ) return;


  String filePath=session.getServletContext().getRealPath("/")+
                "WEB-INF/files/"+board.getSavedFileName();
  System.out.println("filename: "+filePath);
  
  BufferedOutputStream out=null;
  InputStream in=null;
  try {
   response.setContentType("image/jpeg");
   response.setHeader("Content-Disposition", "inline;filename="+board.getFilename());
   File file=new File(filePath);
   if ( checkFileType(file)) {
     in=new FileInputStream(file);
     out=new BufferedOutputStream(response.getOutputStream());
     int len;
     byte[] buf=new byte[1024];
     while ( (len=in.read(buf)) > 0) {
    out.write(buf,0,len);
     }
   }
  }catch(Exception e){
   e.printStackTrace();
   System.out.println("파일 전송 에러");
  } finally {
   if ( out != null ) try { out.close(); }catch(Exception e){}
   if ( in != null ) try { in.close(); }catch(Exception e){}
  }
   }
 }



==> 이미지를 요청하는 HTML 

 <c:if test="${board.fileName != null }">
  <tr>
   <td colspan="4" align="left">
   <span class="date">첨부파일:&nbsp;
   <a href="get_image.do?idx=${board.idx}" 
       target="_blank">${board.fileName}</a></span></td>
  </tr>
  </c:if> 

==> BoardController.java




BoardController (3).java






<참고> MS IIS 파일 확장자 처리오류 취약점 주의


□ 개요
   o 마이크로소프트사의 IIS(Internet Information Service)에서 파일 확장자 처리오류로 인한
     보안우회 취약점이 발견됨[1,2,3]
   o 현재 해당 취약점에 대한 보안 업데이트가 발표되지 않았으며 IIS는 국내에서 많이 이용되기
     때문에 해당 서버 관리자는 보안 업데이트가 발표되기 전까지 주의를 요함

□ 영향을 받는 시스템
   o Microsoft Internet Information Services 6.x 이하의 모든 버전

□ 영향을 받지 않는 시스템
   o Microsoft Internet Information Services 7.5

□ 취약점 설명
   o 마이크로소프트의 IIS에서 세미콜론을 이용한 파일 확장자 처리오류를 통해 공격자는
     임의의 파일(웹쉘, Exploit 코드 및 각종 실행파일 등)을 실행할 수 있음 [1,2,3] 
     – 예를 들어 IIS는 “malicious.asp;.jpg” 파일을 ASP 파일로 처리하여 실행
     – 특히 대부분의 파일 업로드 보호 시스템은 파일의 마지막 확장자(JPG)만을 확인하여
       업로드하기 때문에 쉽게 업로드가 가능

□ 영향
   o 만약 상기 취약점을 이용하여 공격한 경우 공격자는 취약한 웹서버의 파일 업로드 기능을
     이용하여 웹쉘을 업로드 및 실행한 후 웹서버에 대한 완벽한 권한을 취할 수 있음
   o 또한 악의적인 파일을 업로드한 후 인터넷 사용자의 클릭을 유도하여 사용자 PC에 악성코드를
     감염시킬 수 있음

□ 임시 조치방법
   o 파일의 이름 및 확장자를 랜덤한 문자열로 치환하여 업로드 되도록 함 [3]
     ※ 사용자가 입력한 파일이름으로 업로드 되지 않도록 함
   o 업로드 파일 디렉토리에 대한 실행 권한을 해제 [3]
     ※ 설정방법 (Windows 2003 사용자 환경의 예)
      – “제어판 > 관리 도구 > 인터넷 정보 서비스(IIS) 관리”에서 업로드 폴더의 속성 클릭
      

      – “디렉터리 탭”의 “실행 권한”을 “없음”으로 설정
      

□ 용어 정리
   o IIS(Internet Information Service): 인터넷정보서비스라 불리며 마이크로소프트 윈도우즈를
     사용하는 서버들을 위한 인터넷 기반 서비스
   o ASP(Active Server Script): 마이크로소프트사에서 개발한 서버 측의 스크립팅 환경

□ 기타 문의사항
   o 보안업데이트는 언제 발표되나요?
     – MS의 공식 보안업데이트 일정은 발표되지 않음, 발표될 경우 KrCERT 홈페이지를 통해
       신속히 공지할 예정입니다
   o 한국인터넷진흥원 인터넷침해대응센터: 국번없이 118

□ 참고사이트
   [1] http://secunia.com/advisories/37831/ (Secunia)
   [2] http://www.vupen.com/english/advisories/2009/3634
   [3] http://soroush.secproject.com/downloadable/iis-semicolon-report.pdf