김솔비 블로그
트러블 슈팅

SQL Injection·XSS 시도가 담긴 자동화 요청 대응: 인증 없는 API에 JWT 인증 적용

3분 읽기

JSP로 구성된 회사 홈페이지를 운영하면서 겪은 보안 이슈와, 이를 JWT 기반 API 인증으로 해결한 과정을 정리한다.

증상

문의사항 게시판에 고객 문의가 아닌 비정상적인 글이 반복해서 등록되었다. 글 내용에는 SQL 쿼리문 조각이나 스크립트처럼 보이는 문자열 등, 사람이 문의 폼을 채워서 쓴 것으로 보기 어려운 값들이 들어 있었다.

원인

누군가 문의 작성 화면을 거치지 않고 문의 등록 API를 인위적으로 직접 호출하고 있었다.

당시 구조의 문제는 이 API가 호출한 사람이 누구인지 확인하지 않았다는 점이다. 주소와 파라미터 형식만 알면 브라우저가 아니라 스크립트로도 얼마든지 글을 등록할 수 있었다. 화면에 둔 입력 제한은 화면을 통해 들어올 때만 의미가 있고, API를 직접 부르면 모두 건너뛸 수 있다.

글에 섞여 있던 SQL·스크립트 문자열은 이런 자동화 요청이 흔히 보내는 취약점 탐색용 입력값으로, 각각 SQL Injection과 XSS(Cross-Site Scripting) 가 통하는지 시험하는 시도다. 다만 시도가 담긴 글이 저장되었다는 것과 공격이 실제로 성공했다는 것은 다른 이야기다. 즉 이 사건의 본질은 특정 공격 기법보다 인증 없이 열려 있는 쓰기 API에 있었다.

조치: 로그인 방식을 JWT로 전환하고 API 호출에 토큰 요구

기존 로그인 방식을 JWT(JSON Web Token) 기반으로 바꾸고, API를 호출할 때 토큰을 함께 보내도록 했다. 서버는 요청마다 토큰을 검증하고, 유효한 토큰이 없는 요청은 처리하지 않는다.

  1. 사용자가 로그인하면 서버가 JWT를 발급한다
  2. 클라이언트는 API를 호출할 때 Authorization: Bearer <토큰> 헤더에 토큰을 담아 보낸다
  3. 서버는 요청을 처리하기 전에 토큰의 서명과 만료 시간을 검증한다
  4. 검증을 통과하지 못한 요청은 거부한다

서버 쪽 검증은 요청이 컨트롤러에 닿기 전에 토큰을 확인하는 필터로 처리한다. 구조를 보여 주기 위한 예시는 다음과 같다.

java
// 예시: 요청 헤더의 JWT 를 검증하는 필터 public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtProvider jwtProvider; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); if (jwtProvider.validate(token)) { // 서명·만료 검증 Authentication auth = jwtProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(auth); } } chain.doFilter(request, response); } }

결과

토큰 없이 API를 직접 호출하는 요청은 더 이상 처리되지 않았고, 문의 게시판에 비정상적인 글이 쌓이던 문제가 해결되었다.

정리

  • 화면에서 막는 것은 보안이 아니다. 화면에 버튼이 없어도 API는 직접 호출할 수 있다. 서버가 요청마다 "누가 보냈는지"를 확인해야 한다.
  • 쓰기 API는 기본적으로 인증이 필요한 상태로 시작해야 한다. 이번 문제는 인증을 붙이는 것으로 근본 원인이 해결되었다.
  • 인증과 입력값 검증은 별개의 문제다. JWT 인증은 "아무나 호출하지 못하게" 막는 장치다. 로그인한 사용자가 보내는 입력값 자체의 안전성(SQL Injection, XSS 등)은 파라미터 바인딩과 출력 시 이스케이프 처리처럼 별도의 방법으로 다뤄야 한다.
  • 남은 과제: 회원가입이 열려 있다면 자동화 도구도 계정을 만들어 토큰을 받을 수 있다. 요청 횟수 제한(Rate Limiting)이나 CAPTCHA를 함께 두면 한 단계 더 막을 수 있다.