BE/Java

[Java] SQL Injection, PreparedStatement

셰욘 2025. 1. 9. 12:48
728x90

로그인 기능 코드

login.jsp

<form action="/user/login" method="post">
    <div class="d-flex flex-column justify-content-center align-items-center">
        <div class="col-lg-6 col-md-6">
            <input type="text" name="email" placeholder="이메일을 입력해주세요.">
        </div>
        <div class="col-lg-6 col-md-6">
            <input type="password" name="password" placeholder="비밀번호를 입력해주세요.">
        </div>
        <div class="col-lg-12 text-center">
            <button type="submit" class="site-btn">로그인</button>
        </div>
    </div>
</form>

 

 

UserController.java

package controller;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import model.User;
import service.UserService;

import java.io.IOException;

@WebServlet("/user/*")
public class UserController extends HttpServlet {

    private UserService userService;

    public UserController() {
        userService = new UserService();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action = req.getPathInfo();
        if ("/login".equals(action)) {
            req.getRequestDispatcher("/view/user/login.jsp").forward(req, resp);
        } else if ("/signup".equals(action)) {
            req.getRequestDispatcher("/view/user/signup.jsp").forward(req, resp);
        } else if ("/profile".equals(action)) {
            User user = userService.getProfile(req.getParameter("username"));
            req.setAttribute("user", user);
            req.getRequestDispatcher("/view/user/profile.jsp").forward(req, resp);
        }

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action = req.getPathInfo();
        if ("/signup".equals(action)) {
            User user = new User(
                    req.getParameter("email").replaceAll("/", ""),
                    req.getParameter("password"),
                    req.getParameter("name")
            );
            userService.signup(user);
            resp.sendRedirect("/user/login");
        } else if ("/login".equals(action)) {
            User user = new User(
                    req.getParameter("email"),
                    req.getParameter("password")
            );
            boolean result = userService.login(user);
            if (result) {
                resp.sendRedirect("/");

                req.getSession().setAttribute("isLogin", true);
                req.getSession().setAttribute("userName", user.getEmail());

                return;
            }
            resp.sendRedirect("/user/login");
        }
    }
}

 


SQL Injection 

 

이메일을 입력하는 창에 이렇게 입력하면 비밀번호를 아무렇게나 입력해도 로그인이 된다.

'abcd' OR '1'='1' --

SELECT * FROM user WHERE email = 'abcd' OR '1'='1' -- AND password='qwer';

 

 

뒤의 조건 ('1'='1')이 무조건 참이기 때문에 이메일을 어떤 걸 입력해도 참이 된다.

-- 는 SQL 코드를 주석 처리 해주기 때문에 뒤의 AND password ~ 가 주석처리 된다.

 

이메일을 위에처럼 입력하게 되면 sql문 결과는 "SELECT * FROM USER" 와 같은 결과값이 나온다. = 모든 행이 조회된다.

 

조회된 결과가 있으니까 로그인 성공이라고 판단해서 로그인이 성공 처리가 된다.

ResultSet rs = conn.createStatement().executeQuery(sql);
if (rs.next()) {
    return true;
}
return false;

 

 


SQL Injection 막는 법

1. 입력값 검증

사용자가 입력한 값에서 사용하면 안 되는 값이 있나 확인한다.

사용자 요청 받는 부분에 replace특수 문자들공백으로 바꾼다.

req.getParameter("email").replaceAll("[/\\\\\";:'*#-]", "");

 


2
. 검증 로직 변경

사용자가 입력한 부분에서 이메일만 갖고온 다음에 if 문으로 비밀번호가 맞는지 확인

String sql = "SELECT * FROM user WHERE " + "email = '" + user.getEmail() + "'";

ResultSet rs = conn.createStatement().executeQuery(sql);
if(rs.getString("password").equals(user.getPassword())) {}

 

 

3. PreparedStatement로 SQL 실행 ⭐

sql을 문자열로 직접 편집하지 말고, 쓰고 싶은 sql이 있으면 빈 칸(물음표)으로 뚫어놓고 빈 칸에 값을 집어넣음

ResultSet을 바로 반환하는 게 아니라 PrepareStatement로 반환하고, 여기서 값을 넣고 세팅하기

String sql = "SELECT * FROM user WHERE email = ? AND password = ?";

try {
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, user.getEmail());
    pstmt.setString(2, user.getPassword());
    
    ResultSet rs = pstmt.executeQuery();
    if(rs.next()) {
    	return true;
    } 
    return false;
} catch { 
    //.. 
}

 


Statement vs PreparedStatment

Statement로 SQL 실행

  • SQL을 실행할 때마다 SQL을 실행 과정을 거쳐서 실행
  • 실행과정 : 파싱 -> 최적화 -> Row 소스 생성 -> 실행
  • 간단한 쿼리에 적합

 

PreparedStatment로 SQL 실행

  • 실행 과정을 미리 수행해두고 빈 칸에 값을 넣어서 실행
  • 작업할 때 리소스를 더 많이 잡아먹고, Statement 보다 더 오래 걸린다.
  • 실행 과정 : 파싱 -> 최적화 -> 미완성의 Row 소스 생성 -> Row 소스에 값 넣어서 완성 -> 실행

 

 

728x90