|
| 1 | +--- |
| 2 | +title: "[Security] Secure Coding(4-3) - 정규식 활용(입력값 필터링)" |
| 3 | + |
| 4 | +categories: [Security, Secure Coding] |
| 5 | +tags: |
| 6 | + - [Security, Cyberattacks, 보안, 시큐어 코딩, 정규식, 필터링] |
| 7 | +toc: true |
| 8 | +toc_sticky: true |
| 9 | + |
| 10 | +date: 2025-09-24 |
| 11 | +last_modified_at: 2025-09-24 |
| 12 | +--- |
| 13 | +>🔒 시큐어 코딩 수업 정리 |
| 14 | +
|
| 15 | +## 입력값 필터 만들기 |
| 16 | +📚**<span style="color: #008000">필터(filter)</span>**: **클라이언트의 HTTP 요청과 응답을 가로채어 특정 작업을 수행**하는 서블릿 기반 컴포넌트 |
| 17 | + |
| 18 | +* 웹 애플리케이션에서 보안, 로깅, 데이터 변환등의 작업을 수행할 때 사용 |
| 19 | + |
| 20 | +✅**주요 역할**: |
| 21 | +* 요청/응답 로깅 |
| 22 | +* 인증(Authentication) & 권한 검사(Authorization) |
| 23 | +* CORS 처리(다른 도메인의 요청 허용 또는 차단) |
| 24 | +* 데이터 검증 & 변환(요청 데이터 가공 및 유효성 검사) |
| 25 | +* 응답변환(JSON,XML 등 특정형식으로 변경) |
| 26 | +* 보안강화(SQL Injection, XSS 등의 공격 방어) |
| 27 | + |
| 28 | + |
| 29 | +> Spring Boot Filter 구조 |
| 30 | +
|
| 31 | +--- |
| 32 | + |
| 33 | +### 필터 구현 방법 |
| 34 | + |
| 35 | +#### **1. 기본 필터 클래스 구현** |
| 36 | + |
| 37 | +```java |
| 38 | +public class CustomFilter implements Filter { |
| 39 | + |
| 40 | + @Override |
| 41 | + public void init(FilterConfig filterConfig) throws ServletException { |
| 42 | + // 필터 초기화 (서버 시작 시 1회 실행) |
| 43 | + } |
| 44 | + |
| 45 | + @Override |
| 46 | + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
| 47 | + throws IOException, ServletException { |
| 48 | + |
| 49 | + HttpServletRequest httpRequest = (HttpServletRequest) request; |
| 50 | + System.out.println("Request URI: " + httpRequest.getRequestURI()); |
| 51 | + |
| 52 | + // 다음 필터 또는 컨트롤러로 진행 |
| 53 | + chain.doFilter(request, response); |
| 54 | + } |
| 55 | + |
| 56 | + @Override |
| 57 | + public void destroy() { |
| 58 | + // 필터 종료 (서버 종료 시 1회 실행) |
| 59 | + } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +* `init()`: 필터가 생성될 때 딱 한 번 실행 (초기화 작업) |
| 64 | +* `doFilter()`: 요청이 올 때마다 실행되는 핵심 로직 |
| 65 | +* `destroy()`: 필터가 제거될 때 실행 (정리 작업) |
| 66 | +* `chain.doFilter()`: 매우 중요! 다음 단계로 넘어가는 명령 |
| 67 | + |
| 68 | +#### **2. 필터를 Spring Boot에 등록** |
| 69 | +* **(방법 1)** `@Component` 어노테이션 사용 - 자동 등록 |
| 70 | + |
| 71 | +```java |
| 72 | +@Component |
| 73 | +public class CustomFilter implements Filter { |
| 74 | + |
| 75 | + @Override |
| 76 | + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
| 77 | + throws IOException, ServletException { |
| 78 | + |
| 79 | + HttpServletRequest httpRequest = (HttpServletRequest) request; |
| 80 | + System.out.println("Request Method: " + httpRequest.getMethod()); |
| 81 | + |
| 82 | + chain.doFilter(request, response); |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +* 장점: 코드가 간단함 |
| 88 | +* 단점: 필터 순서 지정 불가, URL 패턴 세밀하게 설정 불가 |
| 89 | + |
| 90 | +* **(방법 2)** `FilterRegistrationBean` 사용하여 수동 등록(우선순위 지정이 필요한 경우) |
| 91 | + |
| 92 | +```java |
| 93 | +@Configuration |
| 94 | +public class FilterConfig { |
| 95 | + |
| 96 | + @Bean |
| 97 | + public FilterRegistrationBean<CustomFilter> loggingFilter() { |
| 98 | + FilterRegistrationBean<CustomFilter> registrationBean = new FilterRegistrationBean<>(); |
| 99 | + registrationBean.setFilter(new CustomFilter()); |
| 100 | + registrationBean.addUrlPatterns("/api/*"); // 특정 URL 패턴에만 적용 |
| 101 | + registrationBean.setOrder(1); // 필터 실행 순서 지정 (낮을수록 먼저 실행) |
| 102 | + return registrationBean; |
| 103 | + } |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +* 장점: 필터 실행 순서 제어 가능, URL 패턴 세밀하게 설정 가능, 여러 필터 관리 용이 |
| 108 | + |
| 109 | +#### Spring Security와 기본 필터 |
| 110 | + |
| 111 | +```java |
| 112 | +@Configuration |
| 113 | +public class DefaultSecurityConfig { |
| 114 | + |
| 115 | + @Bean |
| 116 | + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { |
| 117 | + http |
| 118 | + .authorizeHttpRequests(auth -> auth |
| 119 | + .anyRequest().authenticated() // 모든 요청은 인증 필요 |
| 120 | + ) |
| 121 | + .formLogin() // 기본 로그인 폼 활성화 |
| 122 | + .httpBasic(); // HTTP Basic 인증 활성화 |
| 123 | + |
| 124 | + return http.build(); |
| 125 | + } |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +1. `.authorizeHttpRequests()`: 어떤 요청에 인증이 필요한지 설정 |
| 130 | +2. `.formLogin()`: 로그인 페이지 자동 생성 |
| 131 | +3. `.httpBasic()`: HTTP Basic 인증 방식 사용 |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +### XSS 공격 |
| 136 | +📚**<span style="color: #008000">XSS (Cross-Site Scripting)</span>**: 게시판처럼 입력 내용 그대로 표출되는 곳에 악성 스크립트를 넣어, 서비스 이용자의 브라우저에서 실행되도록 하는 공격 |
| 137 | + |
| 138 | +💡**예시**: |
| 139 | +* 게시판에 글을 쓸 때: |
| 140 | + * 공격자: <script>alert('해킹!');</script> 라고 입력 |
| 141 | + * 결과: 게시글을 보는 모든 사람의 브라우저에서 경고창이 뜸! 😱 |
| 142 | + |
| 143 | +``` |
| 144 | +실제 피해 사례: |
| 145 | +❌ 쿠키/세션 탈취 → 계정 도용 |
| 146 | +❌ 사용자 정보 수집 |
| 147 | +❌ 악성 사이트로 리다이렉트 |
| 148 | +❌ 키로깅 (입력 내용 훔치기) |
| 149 | +❌ 가짜 로그인 페이지 표시 |
| 150 | +``` |
| 151 | + |
| 152 | +* **🛡️ XSS 방어 전략** |
| 153 | +* 사용자 입력값에서 위험한 패턴의 문자열을 필터링 |
| 154 | +* SpringBoot의 ServletFilter와 HttpServletRequestWrapper를 이용하여 모든 요청의 입력값을 가로챔 |
| 155 | +* 입력값의 내용을 정규식으로 패턴을 확인하여 필터링 처리 |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | +#### XSS 필터 구현 |
| 160 | +1. **XSSFilter 만들기** |
| 161 | + |
| 162 | +```java |
| 163 | +@Component |
| 164 | +public class p043_XSSFilter implements Filter { |
| 165 | + |
| 166 | + @Override |
| 167 | + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
| 168 | + throws IOException, ServletException { |
| 169 | + |
| 170 | + // 1. 일반 Request를 HttpServletRequest로 변환 |
| 171 | + HttpServletRequest httpRequest = (HttpServletRequest) request; |
| 172 | + |
| 173 | + // 2. 🔑 핵심: Request를 XSSRequestWrapper로 감싸기 |
| 174 | + chain.doFilter(new p043_XSSRequestWrapper(httpRequest), response); |
| 175 | + |
| 176 | + // 이제 Controller에서 받는 모든 파라미터는 |
| 177 | + // XSSRequestWrapper를 거쳐서 자동으로 필터링됨 |
| 178 | + } |
| 179 | +} |
| 180 | +``` |
| 181 | + |
| 182 | +2. **XSSRequestWrapper 만들기** |
| 183 | + |
| 184 | +```java |
| 185 | +public class p043_XSSRequestWrapper extends HttpServletRequestWrapper { |
| 186 | + |
| 187 | + public p043_XSSRequestWrapper(HttpServletRequest request) { |
| 188 | + super(request); |
| 189 | + } |
| 190 | + |
| 191 | + // 1. 단일 파라미터 가져올 때 |
| 192 | + @Override |
| 193 | + public String getParameter(String name) { |
| 194 | + String value = super.getParameter(name); |
| 195 | + return (value != null) ? sanitize(value) : null; |
| 196 | + } |
| 197 | + |
| 198 | + // 2. 여러 파라미터 값 가져올 때 (checkbox 등) |
| 199 | + @Override |
| 200 | + public String[] getParameterValues(String name) { |
| 201 | + String[] values = super.getParameterValues(name); |
| 202 | + if (values != null){ |
| 203 | + // 배열의 모든 값을 sanitize 처리 |
| 204 | + for (int i = 0; i < values.length; i++) { |
| 205 | + values[i] = sanitize(values[i]); |
| 206 | + } |
| 207 | + } |
| 208 | + return values; |
| 209 | + } |
| 210 | + |
| 211 | + // 3. 핵심 메서드: 위험한 문자열 제거 |
| 212 | + private String sanitize(String input) { |
| 213 | + if (input == null) return null; |
| 214 | + |
| 215 | + String clean = input; |
| 216 | + |
| 217 | + // (1) <script> 태그 제거 |
| 218 | + clean = clean.replaceAll("(?i)<script.*?>.*?</script>", ""); |
| 219 | + |
| 220 | + // (2) onclick, onerror 등 이벤트 핸들러 제거 |
| 221 | + clean = clean.replaceAll("(?i)on\\w+\\s*=\\s*[\"'][^\"']*[\"']", ""); |
| 222 | + |
| 223 | + // (3) javascript: 프로토콜 제거 |
| 224 | + clean = clean.replaceAll("(?i)javascript:", ""); |
| 225 | + |
| 226 | + return clean; |
| 227 | + } |
| 228 | +} |
| 229 | +``` |
| 230 | + |
| 231 | +--- |
| 232 | + |
| 233 | +## LAB1 - 소스코드의 XSS 취약점 확인 |
| 234 | + |
| 235 | +1. 환경 구축한 LAB을 실행시켜 크롬으로 로그인 |
| 236 | +2. 게시판 메뉴 진입 |
| 237 | +3. '쓰기' 버튼을 눌러 게시글 작성 |
| 238 | +4. 제목 부분에 아래와 같이 입력 |
| 239 | +`안녕하세요<script>alert('XSS!!');</script>` |
| 240 | + |
| 241 | +5. 화면 아래로 내려가 '확인' 버튼 |
| 242 | +6. 게시판 목록 화면에서 해당 게시글 제목에 스크립트 내용이 보여짐 |
| 243 | +7. 해당 게시글을 클릭하면 스크립트가 동작하여 화면에 |
| 244 | +경고창이 나타남 |
| 245 | + |
| 246 | +{:.prompt-warning} |
| 247 | +> XSS 취약점 있는 것이 확인됨 |
| 248 | +> |
| 249 | +
|
0 commit comments