Refactor Summernote initialization for consistency and improved readability

This commit is contained in:
Rinjae
2025-12-30 21:37:37 +09:00
parent 9a1e73ec4f
commit a7fe811fb9
9 changed files with 320 additions and 144 deletions
@@ -15,12 +15,9 @@ class RequestWrapper extends HttpServletRequestWrapper {
// GS인증 크로스사이트스크립트 보안성 문제로 XSS 공격을 방지하기 위한 악성 스크립트 패턴 정의.
// 2025.05.27 패턴 추가
// 2025.12.30 src 패턴 수정 - 화이트리스트 방식 (http, https, data:image/ 만 허용)
private static final Pattern[] PATTERNS = new Pattern[] {
Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("<script(.*?)>",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
@@ -46,6 +43,16 @@ class RequestWrapper extends HttpServletRequestWrapper {
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
};
// src 속성 화이트리스트 패턴 - data:image/만 허용
private static final Pattern SRC_ATTR_PATTERN = Pattern.compile(
"(src\\s*=\\s*)([\"'])([^\"']*?)\\2",
Pattern.CASE_INSENSITIVE);
// 허용되는 src 값 패턴 (화이트리스트) - data:image/만 허용
private static final Pattern ALLOWED_SRC_PATTERN = Pattern.compile(
"^data:image/.*",
Pattern.CASE_INSENSITIVE);
public RequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
@@ -123,6 +130,10 @@ class RequestWrapper extends HttpServletRequestWrapper {
// script 문자열을 완전히 제거
value = value.replaceAll("(?i)\\bscript\\b", "");
// src 속성 화이트리스트 필터링 - 허용된 프로토콜만 통과
value = filterSrcAttribute(value);
StringBuilder sb = new StringBuilder();
value = convertChars(value, sb);
@@ -133,6 +144,37 @@ class RequestWrapper extends HttpServletRequestWrapper {
return value;
}
/**
* src 속성을 화이트리스트 방식으로 필터링
* 허용: data:image/ (Base64 이미지)
* 비허용: 그 외 모든 프로토콜
*/
private String filterSrcAttribute(String value) {
if (value == null || !value.toLowerCase().contains("src")) {
return value;
}
Matcher matcher = SRC_ATTR_PATTERN.matcher(value);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String srcPrefix = matcher.group(1); // src=
String quote = matcher.group(2); // " 또는 '
String srcValue = matcher.group(3); // src 값
// 빈 값이거나 화이트리스트에 매칭되면 유지
if (srcValue.isEmpty() || ALLOWED_SRC_PATTERN.matcher(srcValue).matches()) {
matcher.appendReplacement(result, Matcher.quoteReplacement(srcPrefix + quote + srcValue + quote));
} else {
// 허용되지 않는 프로토콜은 src="" 로 대체
matcher.appendReplacement(result, Matcher.quoteReplacement(srcPrefix + quote + quote));
}
}
matcher.appendTail(result);
return result.toString();
}
// '(', ')', '"' 문자는 getMethod()가 "get"일 때만 변환됨.
// 이는 gridData에서 사용되는 큰따옴표(")와 변환 함수에서 사용하는 괄호('(', ')')의 처리를 위함.
private String convertChars(String value, StringBuilder sb) {