21.04.07

EDDIE Kim·2021년 4월 8일
0

KH

목록 보기
76/98

XSS



웹사이트에 script가 적용되서 내용이 다 날라가버림 - 해킹의 일종에 script가 적용되서 내용이 다 날라가버림 - 해킹의 일종

태그를 무력화시키고 문자그대로 실행되게 해야함

첨부파일 링크 클릭 시 다운로드

<img alt="첨부파일" src="<%=request.getContextPath() %>/images/file.png" width=16px>
				<a href="<%= request.getContextPath() %>/board/fileDownload?no=<%= board.getNo() %>"><%= board.getAttach().getOriginalFileName() %></a>
				<% } %>
                

package board.controller;

@WebServlet("/board/fileDownload")
public class FileDownloadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	BoardService boardService = new BoardService();

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1. 사용자 입력값 : no 게시글 번호
		int no = Integer.parseInt(request.getParameter("no"));
		
		//2. 업무로직 : 첨부파일 조회
		//originalFileName, renamedFileName
		Attachment attach = boardService.selectBoardByNo(no).getAttach();
		System.out.println("attach@servlet = " + attach);
		
		//3. 파일 다운로드
		//a. 입출력스트림 생성
		String saveDirectory = getServletContext().getRealPath("/upload/board");
		//new File(저장될 경로, 저장될 파일명)
		File f = new File(saveDirectory, attach.getRenamedFileName());
		
		BufferedInputStream bis = 
				new BufferedInputStream(new FileInputStream(f));
		
		//대상이 응답메세지인 출력 스트림 가져오기
		BufferedOutputStream bos = 
				new BufferedOutputStream(response.getOutputStream());
		
		//b. 응답헤더작성
		//파일명 한글로 인코딩 처리
		//ISO-8859-1(톰캣용)으로 처리해서 헤더에 넣으면, 다운받을 때 톰캣이 다시 인코딩을 해준다
		String responseFileName = new String(attach.getOriginalFileName().getBytes("utf-8"), "ISO-8859-1");
		//서버단에선 깨져서 나온다
		System.out.println("responseFileName = " + responseFileName);
		response.setContentType("application/octet-stream; charset=utf-8"); //2진데이터
		response.setHeader("Content-Disposition", "attachment;filename=" + responseFileName);
		
		//c. 파일출력
		int read = -1;
		while((read = bis.read()) != -1) {
			bos.write(read);
		}
		
		//d. 자원반납
		bos.close();
		bis.close();
	}

}

FileDownloadServlet.java로 이동해서 작성

한글이 깨지지 않도록 작성해주기

게시글 삭제하기(작성자, 관리자만)

<%@page import="board.model.vo.Board"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/views/common/header.jsp" %>
**<%
	Board board = (Board)request.getAttribute("board");
	boolean editable = 
			loginMember != null && 
			(loginMember.getMemberId().equals(board.getWriter()) 
					|| MemberService.ADMIN_ROLE
.equals(loginMember.getMemberRole()));		
%>**
<link rel="stylesheet" href="<%=request.getContextPath()%>/css/board.css" />
<section id="board-container">
	<h2>게시판</h2>
	<table id="tbl-board-view">

//중략

**<% if(editable){ %>
			<%-- 작성자와 관리자만 마지막행 수정/삭제버튼이 보일수 있게 할 것 --%>
			<th colspan="2">
				<input type="button" value="수정하기" onclick="updateBoard()">
				<input type="button" value="삭제하기" onclick="deleteBoard()">
			</th>
			<% } %>**
		</tr>
	</table>
</section>
<form 
	action="<%= request.getContextPath() %>/board/boardDelete" 
	name="boardDelFrm"
	method="POST">
	<input type="hidden" name="no" value="<%= board.getNo() %>" />
</form>
<script>
**function deleteBoard(){
	if(confirm("게시글을 정말 삭제하시겠습니까?")){
		$(document.boardDelFrm).submit(); //확인 시, 히든폼 제출
	}
}**
</script>
<%@ include file="/WEB-INF/views/common/footer.jsp" %>

boardView.jsp으로 이동해서 작성로 이동해서 작성

on delete cascade속성이라 게시글만 지우면 첨부파일 행도 알아서 지워짐

예외처리

public int deleteBoard(int no) {
		Connection conn = getConnection();
		int result = 0;
		
		try {
			result = boardDao.deleteBoard(conn, no);
			if(result == 0)
				throw new IllegalArgumentException("해당 게시글이 존재하지 않습니다. : " + no);
			
			commit(conn);
		} catch(Exception e) {
			e.printStackTrace(); //controller에서 예외메세지 표시
			rollback(conn);
			throw new BoardException("게시물 삭제 오류", e); // controller가 예외처리를 결정할 수 있도록 넘김
		} finally {
			close(conn);
		}
		return result;
	}

BoardService.java로 이동해서 작성
service에서 트랜잭션처리 할 때 try catch처리

운영 중에 서버의 오류페이지를 노출하면 안됨
에러코드는 절대 유출되면 안됨. 보안사고를 유발할 수 있음

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			int no = Integer.parseInt(request.getParameter("no"));
			System.out.println("no@servlet = " + no);
			int result = boardService.deleteBoard(no);
			
			HttpSession session = request.getSession();
			System.out.println("result@deleteServlet = " + result);
			
			if(result > 0) {
				session.setAttribute("msg", "게시물이 삭제되었습니다");
				//boardList로 redirection
				response.sendRedirect(request.getContextPath() + "/board/boardList");
			}
			else {
				session.setAttribute("msg", "게시물 삭제에 실패했습니다.");
			}
		}catch(Exception e) {
			//예외 로깅을 컨트롤러에서 한다
			e.printStackTrace();
			//예외페이지 전환을 위해서 was에 예외 다시 던지기
			throw e;
		}
	}

BoardDeleteServlet.java로 이동해서 작성
try/catch절로 감싸고 톰캣에 에러를 던진다.

커스텀 예외페이지 jsp 생성

페이지 지시어에 isErrorPage속성을 true로 지정하면, 던져진 예외객체에 exception 키워드로 선언없이 접근 가능

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isErrorPage="true"%>
<%--
	isErrorPage속성을 true로 지정하면, 
	던져진 예외객체에 exception 키워드로 선언없이 접근 가능
--%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body style="text-align: center;">
	<h1>이용에 불편을 드려 죄송합니다.</h1>
	<p style="color: red;"><%= exception.getMessage() %></p>
//servlce 또는 dao에서 던진 에러메세지가 커스텀 에러 페이지를 표시
	<p><a href="<%= request.getContextPath() %>">메인페이지로 돌아가기</a></p>
</body>
</html>


web.xml로 이동해서

<error-page>
 	<exception-type>java.lang.Exception</exception-type>
 	<location>/WEB-INF/views/common/error.jsp</location>
 </error-page>

추가해주기

예외처리시 브라우져에 출력되는 화면

예외처리의 흐름

service와 dao에서는 에러를 던져서 (throw new BoardException() : RuntimeException 을 상속받은 클래스)컨트롤러에서 반드시 한번은 에러를 잡아야 한다

컨트롤러에서 에러를 잡으면

  • logging (e.printStackTrace())
  • 어드민에 알리고 (notify to admin)
  • 예외를 was에게 다시 던진다

예외를 받은 was는 tomcat-error.jsp나 custom-error.jsp중에 골라서 표시한다

  • dao에서 에러 발생했을 때 dao에서 예외처리해버리면 controller가 알지못하고 예외페이지로 전환되지 않는다

Controller에서는 try catch처리를 꼭 해야한다.

게시글 수정하기(작성자, 관리자만)

//선략
<% if(editable){ %>
			<%-- 작성자와 관리자만 마지막행 수정/삭제버튼이 보일수 있게 할 것 --%>
			<th colspan="2">
				<input type="button" value="수정하기" onclick="updateBoard()">
				<input type="button" value="삭제하기" onclick="deleteBoard()">
			</th>
			<% } %>
		</tr>
//중략
</form>
<script>
function updateBoard(){
	location.href="<%= request.getContextPath() %>/board/boardUpdate?no=<%= board.getNo() %>";
}
//후략

boardView.jsp이동해서 작성

public class BoardUpdateServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private BoardService boardService = new BoardService();
	
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.사용자 입력값 
		int no = Integer.parseInt(request.getParameter("no"));
		
		//2.업무로직
		Board board = boardService.selectOne(no);
		
		//3.jsp포워딩
		request.setAttribute("board", board);
		request.getRequestDispatcher("/WEB-INF/views/board/boardUpdateForm.jsp")
			   .forward(request, response);
	}

BoardUpdateServlet.java이동해서 doGet작성

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
	try {
		//1. MultipartRequest객체 생성
		// /WebContent/upload/board/업로드파일명.jpg 
		// web rool dir를 절대경로로 반환
		String saveDirectory = getServletContext().getRealPath("/upload/board");
		System.out.println("saveDirectory@servlet = " + saveDirectory);
			
		//최대파일허용크기 10mb = 10 * 1kb * 1kb
		int maxPostSize = 10 * 1024 * 1024;
			
		//인코딩
		String encoding = "utf-8";
			
		//파일명 변경정책 객체
		//중복파일인 경우, numbering처리
		//filerename : 20210406191919_123.jpg
		//FileRenamePolicy policy = new DefaultFileRenamePolicy();
		FileRenamePolicy policy = new MvcFileRenamePolicy();
			
		MultipartRequest multipartRequest = 
				new MultipartRequest(
							request, 
							saveDirectory, 
							maxPostSize, 
							encoding, 
							policy
							);
			
		//2. db에 게시글/첨부파일 정보 저장
			
		//2-1. 사용자 입력값처리
		int no = Integer.parseInt(multipartRequest.getParameter("no"));
		String title = multipartRequest.getParameter("title");
		String  writer = multipartRequest.getParameter("writer");
		String content = multipartRequest.getParameter("content");
			
		//업로드한 파일명
		String originalFileName = multipartRequest.getOriginalFileName("upFile");
		String renamedFileName = multipartRequest.getFilesystemName("upFile");
			
		//Board board = new Board(0, title, writer, content, null, 0, null);
		Board board = new Board();
		board.setNo(no);
		board.setTitle(title);
		board.setWriter(writer);
		board.setContent(content);
			
		//첨부파일이 있는 경우
		//multipartRequest.getFile("upFile"):File != null
		if(originalFileName != null) {
			Attachment attach = new Attachment();
			attach.setBoardNo(no);
			attach.setOriginalFileName(originalFileName);
			attach.setRenamedFileName(renamedFileName);
			board.setAttach(attach);
		}
		
		//2. 업무로직 : db에 insert
		int result = boardService.updateBoard(board);
		String msg = (result > 0) ? "게시글 수정 성공!" : "게시글 수정 실패!";
		String location = request.getContextPath()
						+ "/board/boardView?no=" + board.getNo(); 
		
		//3. DML요청 : 리다이렉트 & 사용자피드백
		// /mvc/board/boardList
		HttpSession session = request.getSession();
		session.setAttribute("msg", msg);
		response.sendRedirect(location);
		
	} catch (Exception e) {
		e.printStackTrace();
		throw e; // was한테 다시 던져서 에러페이지로 전환함.
	}
}

BoardUpdateServlet.java이동해서 doPost작성
BoardEnrollServlet.java의 doPost코드를 가져온 후 board객체에 parameter에서 받은 no값 추가

//선략
<%
	Board board = (Board)request.getAttribute("board");
%>
<section id="board-container">
<h2>게시판 수정</h2>
<form 
	name="boardUpdateFrm" 
	action="<%=request.getContextPath() %>/board/boardUpdate" 
	enctype="multipart/form-data"
	method="post">
	<input type="hidden" name="no" value="<%= board.getNo() %>" />
	<table id="tbl-board-view">
	<tr>
		<th>제 목</th>
		<td><input type="text" name="title" value="<%= board.getTitle() %>" required></td>
	</tr>
	<tr>
		<th>작성자</th>
		<td>
			<input type="text" name="writer" value="<%= board.getWriter() %>" readonly/>
		</td>
	</tr>
	<tr>
		<th>첨부파일</th>
		<td >
		<%-- input:file의 value속성은 보안성을 이유로 임의변경 할 수 없다. --%>
				<% if(board.getAttach() != null){ %>
           <p style ="margin:0">
               <img src="<%= request.getContextPath() %>/images/file.png" width="16px"/>
               <%= board.getAttach().getOriginalFileName() %>
           </p>
           <% } %>
		</td>
	</tr>
	<tr>
		<th>내 용</th>
		<td><textarea rows="5" cols="50" name="content"><%= board.getContent() %></textarea></td>
	</tr>
	<tr>
//후략

BoardUpdateForm.jsp으로 이동해서 작성

  • input:file의 value속성은 보안성을 이유로 임의변경 할 수 없다.
  • No를 보내야 해당 게시글을 수정할 수 있다
  • form페이지에서 input창에 기존 값이 들어가있도록 value에 값대입

board-query.properties에

updateBoard = update board set title = ?, content = ? where no = ?

추가

public int updateBoard(Board board) {
		Connection conn = getConnection();
		int result = 0;
		try {
			//1. board update : board테이블에 게시글이 있는 상황
			result = boardDao.updateBoard(conn, board);
			//2. attachment insert : attachment테이블에 data가 없는 상황
			if(board.getAttach() != null) {
				result = boardDao.insertAttachment(conn, board.getAttach());
			}
			
			commit(conn);
		} catch(Exception e) {
			rollback(conn);
			throw e;
			
		}finally {
			close(conn);
		}
		return result;
	}

BoardDao.java으로 이동해서 작성 : 예외 던지기 전에 롤백처리

첨부파일 수정/삭제

  • 삭제 : 서버엔 데이터가 남아있고 status만 N으로 변경
  • 수정 : 기존파일을 N 으로 처리하고 새 파일을 등록
//선략
<script>
$("[name=upFile]").change(function(){
	console.log($(this).val());
	if($(this).val() != ""){
		//파일 선택
		$("#delFile")
			.prop("checked", true)
			.on('click', function(){
				return false; //한번 체크하면 체크 풀 수 없음
			})
	}
	else{
		//파일 선택 취소 (파일선택창에서 열기 안하고 취소 눌렀을 때)
		$("#delFile")
			.prop("checked", false)
			.off('click'); //return false처리했던 이벤트 헨들러 제거
	}
	
});
//후략

BoardUpdateForm.jsp으로 이동해서 작성

첨부파일 업데이트
기존 첨부파일 무력화

status컬럼의 데이터를 Y로 변경

profile
과거 지상직 / 개발자 지망생

0개의 댓글

관련 채용 정보