본문 바로가기
Web Security/SecureCoding

HTTP 응답 분할

by KkingKkang 2022. 11. 16.

- HTTP 요청에 들어 있는 인자값이 HTTP 응답헤더에 포함되어 사용자에게 다시 전달 될 때 입력값에 CR(Carriage Return)이나 LF(Line Feed)와 같은 개행문자가 존재하면 HTTP응답이 2개 이상으로 분리될 수 있다. 

- 이 경우 공격자는 개행문자를 이용하여 첫 번째 응답을 종료시키고 두 번째 응답에 악의적인 코드를 주입하여 XSS 및 캐시 훼손(cache poisoning) 공격 등을 수행할 수 있다.

CR = \r 

LF = \n

- 외부에서 입력된 인자값을 사용하여 HTTP 응답헤더(Set Cookie 등)에 포함시킬 경우 CR, LF등을 제거하거나 적절한 인코딩 기법을 사용하여 변환한다.

 

안전하지 않은 코드 ▼

throws IOException, ServletException {
	response.setContentType("text/html");
    String author = request.getParameter("authorName");
    Cookie cookie = new Cookie("replidedAuthor", author);
    cookie.setMaxAge(1000);
    response.addCookie(cookie);
    RequestDispatcher frd = request.getRequestDispatcher("cookieTest.jsp");
    frd.forward(request, response);
}

 

안전한 코드 ▼

throws IOException, ServletException {
	response.setContentType("text/html");
    String author = request.getParameter("authorName");
    if (author == null || "".equals(author)) return;
    String filtered_author = author.replaceAll("\r","").replaceAll("\n","");
    Cookie cookie = new Cookie("replidedAuthor", filtered_author);
    cookie.setMaxAge(1000);
    cookie.setSecure(true);
    response.addCookie(cookie);
    RequsetDispatcher frd = request.getReqeustDispatcher("cookieTest.jsp");
    frd.forward(request, response);
 }

외부에서 입력되는 값에 대하여 널 여부를 체크하고, replaceAll을 이용하여 개행문자 \r \n을 제거하여 헤더값이 나누어지는 것을 방지한다. 

▶ RequestDispatcher란? 

- 클라이언트로부터 최초에 들어온 요청을 JSP/Servlet 내에서 원하는 자원으로 요청을 넘기는(보내는) 역할을 수행하거나, 특정 자원에 처리를 요청하고 처리 결과를 얻어오는 기능을 수행하는 클래스이다. 

 

 

안전하지 않은 코드2 ▼

public class ShowRequestHeaders extends HttpServlet {
	private final String CHANGE_PASSWORD_CMD = "change_password";
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	String command = request.getParameter("command");
        if(command.equals(CHAGE_PASSWORD_CMD)) {
        	Statement stmt = con.createStatement();
            String userId = request.getParameter("user_id");
            String newPasswd = request.getParameter("new_password");
            String oldPasswd = request.getParameter("old_password");
            String query = "UPDATE USER_INFO SET id = " + userId + ",passwd = " + newPasswd +
            				"Where passwd = " + oldPasswd;
            stmt.executeUpdate(query);
        }   
    }
}

 

안전한 코드 2 ▼

public class StatementInfo {
	public Statement statement;
    public Array<String> parameterNames;
}

public class ShowRequsetHeaders extends HttpServlet {
	private final String CHAGE_PASSWORD_CMD = "change_password";
    private final String CHAGE_PASSWORD_QUERY_STR = "UPDATE USER_INFO SET id = ?, password = ?" WHERE password = ?;
    private Hashtable<String, StatementInfo> queryStatements;
    
    public void initilize() {
    	queryStatement.put(CHAGE_PASSWORD_CMD, new StatementInfo(con.prepareStatement(CHAGE_PASSWORD_QUERY_STR),"user_id","new_password","old_password");
   	}
    
    public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
    	String command = request.getParameter("command");
        Statement statement = queryStatement.get(command).statement;
        for(int parameterIdx = 0; parameterIdx < queryStatement.get(command).parameterName.size(); parameterIdx++ ) {
        	statemnet.setString(parameterIdx, queryStatement.get(command).parameterNames[parameterIdx];
         }
         statement.excuteQuery();
         
        }
       }

query문을 pre-compile 해두고, 암호 변경의 요청이 있을 때마다 인자 값을 적용하여 사용한다.

pre-compiled 방식을 사용하면 query문을 새로 작성하지 않기 때문에 query를 변조하는 악의적인 사용을 방지할 수 있다. 

 

 

반응형

댓글