저번 시간에는 파일 업로드까지 했습니다. 그럼 이번엔 다운로드를 해야겠죠?
다운로드도 게시판에서 빼놓을 수 없는 기능인데요.
물론 이 기능에서도 보안적인 취약점이 있습니다. 파일 다운로드 취약점으로
권한 없는 악의적인 사용자가 서버의 파일을 다운로드할 수 있는 취약점이죠.
이번 게시글에서도 보안은 신경쓰지않고 어떻게 다운로드가 되는지 흐름을 알아보기 위함입니다.
다운로드를 받기위해선 어떻게 해야할까요?
가장 간단한 방법은 해당 파일의 경로를 a 태그의 href에 파일이름과 명시하면 되는데
이 방법은 서버의 파일경로가 노출되므로 보안상 매우 취약합니다.
페이지 이동하는 것도 이때까지 다운로드해봤을 때 아닌거 같구요.
그래서 꼼수를 쓰기로했습니다.
iframe 태그를 이용하여 페이지의 redirect없이 다운로드할 수 있도록 말이죠.
iframe은 페이지속에 또다른 페이지를 표시하기 위한 태그입니다.
function onDownload(idx){ var o = document.getElementById("ifrm_filedown"); o.src = "download.do?idx="+idx; } </script>
</head>
<body>
<iframe id="ifrm_filedown" style="position:absolute; z-index:1;visibility : hidden;"></iframe> ......
<tr> <th colspan="2"> 첨부파일 </th> <td colspan="8"> <a href="#" onclick="onDownload('${article.idx}')">${article.filename}</a> </td> </tr>
꼼수로 iframe을 이용하여 다운로드가 될 수 있도록 하였습니다.
위에 보시면 download.do?idx=를 통해 해당 게시글의 idx를 통해 파일 이름을 가지고 올겁니다.
download.do? 보니까 무엇을 고쳐야할지 이제 감이 오시죠?
Command.properties에 추가합시다
/download.do=com.board.action.DownloadAction
그렇다면 DownloadAction도 만들어주어야죠.
DownloadAction 접기
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import com.board.beans.board; import com.board.control.CommandAction; import com.board.dao.BoardDao;
public class DownloadAction implements CommandAction{ public String requestPro(HttpServletRequest request, HttpServletResponse response) throws Throwable{ request.setCharacterEncoding("euc-kr"); int idx = Integer.parseInt(request.getParameter("idx")); board article = BoardDao.getInstance().getArticle(idx); String filename = article.getFilename(); String uploadFileName = request.getRealPath("/upload"+"/"+filename); File downFile = new File(uploadFileName); if(downFile.exists() && downFile.isFile()) { try{ long filesize = downFile.length(); response.setContentType("application/x-msdownload"); response.setContentLength((int)filesize); String strClient = request.getHeader("user-agent"); if(strClient.indexOf("MSIE 5.5") != -1){ response.setHeader("content-Disposition", "filename="+filename+";"); } else { filename = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20"); //인코딩 변경과 '+'문자 깨짐 방지 response.setHeader("content-Disposition","attachment;filename="+filename+";"); } response.setHeader("Content-Length", String.valueOf(filesize)); response.setHeader("Content-Transfer-Encoding", "binary"); response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control","private"); byte b[] = new byte[1024]; BufferedInputStream fin = new BufferedInputStream(new FileInputStream(downFile)); BufferedOutputStream outs = new BufferedOutputStream(response.getOutputStream()); int read = 0 ; while((read = fin.read(b))!= -1){ outs.write(b,0,read); } outs.flush(); outs.close(); fin.close(); }catch(Exception e){ System.out.println("Download Exception : " + e.getMessage()); } } else { System.out.println("Download Error : downFile Error [" + downFile + "]");
} return null; } }
접기
간략하게 설명하자면
파일 이름을 받아서, 실제 파일이 들어있는 경로에 설정한 upload폴더와 파일이름을 붙여서
해당 스트링에서 파일을 가져오고, 파일이 존재하지 않으면 에러 처리를 합니다.
BoardDao는 content와 똑같은 기능을하기때문에 getArticle을 재사용했으며
파일 사이즈를 조사하고 content타입과 헤더를 세팅하여 파일을 출력합니다.
그리고 파일다운로드를 별도로 리턴할 페이지가 없기때문에 null로 반환합니다.
이제 업로드, 다운로드도 끝났습니다.
그런데 게시글을 지운다면 파일도 같이 지워져야하는데 아직 그 기능은 만들지 않았습니다.
만들어 봅시다. 게시글을 지울 때 동작하기 때문에 DeleteAction을 고치러 갑니다.
DeleteAction 접기
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import com.board.beans.board; import com.board.control.CommandAction; import com.board.dao.BoardDao;
import java.io.File; import java.sql.*;
public class DeleteAction implements CommandAction{ public String requestPro(HttpServletRequest request, HttpServletResponse response) throws SQLException{ int idx = Integer.parseInt(request.getParameter("idx")); board article = BoardDao.getInstance().getArticle(idx); String filename = article.getFilename(); String uploadFileName=request.getRealPath("/upload")+"/"+filename; File uploadfile = new File (uploadFileName); if(uploadfile.exists() && uploadfile.isFile()){ uploadfile.delete(); BoardDao.getInstance().deleteArticle(idx); } return "list.do"; } }
접기
별로 길지도 않고 어렵지 않습니다.
딱 보시면 보이는 소스..
이로써 Model2까지 다 해보았습니다.
앞으로 Ajax를 이용하여 게시글 목록을 계속 보는것만 남았는데 그 이후(Spring)는 에러를 잡지못해서.. 추후에 해보도록 하겠습니다.
직접 게시판을 만들어보고 글쓰기, 삭제, 수정, 파일업로드, 다운로드 등등을 직접 만들다 보니 많은 도움이 됩니다.
웹해킹에서 주로발생하는 Sql 인젝션 파라미터변조 파일 업로드, 다운로드 취약점 등등 어떻게 발생하는지에 대해
간략적이나마 감이 오기 시작합니다.
나중에 시간이 되면 이 글에서 보안적 취약점을 조치하는 방법까지 기술하도록 해보겠습니다.
수고하셨습니다.
비밀
최근댓글