kccf/.claude/skills/kccf-security-check/SKILL.md
ythong 80b5ed1da4 Initial commit - KCCF 문화원연합회 누리집
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 11:38:15 +09:00

3.4 KiB

name description
kccf-security-check KCCF 코드의 보안 취약점을 검토하고 수정한다. 인증/권한 누락, XSS, SQL 인젝션, 세션 취약점 점검 시 사용한다. '보안 검토', '취약점 확인', '권한 검사', 코드 리뷰 요청 시 적용.

KCCF 보안 검토 스킬

검토 우선순위

  1. 인증 누락 (Critical) — 인증 없이 데이터 접근 가능
  2. XSS (High) — 사용자 입력을 HTML에 직접 출력
  3. SQL 인젝션 (High) — MyBatis ${} 오용
  4. 권한 우회 (High) — 권한 확인 없이 쓰기/삭제
  5. 세션 고정 (Medium) — 로그인 후 세션 재생성 미처리

체크리스트

인증 검증 (Controller)

// ✅ 올바른 패턴 — /sec/ URL에 반드시 존재해야 함
UserVO userVO = (UserVO)session.getAttribute("user");
if(userVO == null || StringUtils.isAllBlank(userVO.getUserId())) {
    return "redirect:/login.do";
}

// ❌ 잘못된 패턴 — 세션 체크 없이 데이터 접근
public String deleteData(VO vo) {
    service.delete(vo);  // 누가 호출했는지 모름
}

확인 항목:

  • /sec/ 접두어 URL을 처리하는 모든 메서드에 세션 검사 존재?
  • Ajax 엔드포인트(*Ajax.do)에도 세션 검사 또는 Spring Security 설정?
  • 삭제/등록/수정 메서드에 세션 검사?

XSS 방지 (JSP)

<%-- ✅ 안전 --%>
<c:out value="${userInput}"/>
${fn:escapeXml(userInput)}

<%-- ❌ 위험 --%>
${userInput}
<%= request.getParameter("name") %>

예외 허용 케이스:

  • CKEditor HTML 편집기 출력 (서버 측 sanitize 전제)
  • 개발자가 명시적으로 HTML을 허용한 필드

SQL 인젝션 (Mapper XML)

<!-- ✅ 안전: PreparedStatement -->
WHERE NM = #{searchNm}

<!-- ❌ 위험: String substitution -->
WHERE NM = '${searchNm}'
ORDER BY ${sortColumn}    ← ORDER BY 동적 처리 주의

ORDER BY 동적 처리 시 화이트리스트:

// Controller 또는 Service에서
String[] allowedColumns = {"REG_DTTM", "NM", "CODE"};
if (!Arrays.asList(allowedColumns).contains(sortColumn)) {
    sortColumn = "REG_DTTM";  // 기본값
}

권한 검증

// 쓰기 권한 확인
searchVO.setCanWrite(AuthUtil.canWrite(getUserId(), getSiteSeCode(), getSelMenuId(), ""));

// JSP에서 확인
<c:if test="${searchVO.canWrite}">
    <button onclick="fnDelete()">삭제</button>
</c:if>

주의: JSP 버튼 숨기기만으로는 불충분 — Controller에서도 권한 재확인 필요.

파일 업로드

// ✅ 허용 확장자 검증
String[] allowedExt = {".jpg", ".jpeg", ".png", ".gif", ".pdf", ".hwp", ".xlsx"};
String ext = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
if (!Arrays.asList(allowedExt).contains(ext)) {
    throw new IllegalArgumentException("허용되지 않는 파일 형식");
}

// ❌ 위험: 확장자 검증 없이 저장
String savedPath = uploadDir + originalFileName;

수정 우선순위 결정

등급 기준 처리
Critical 인증 없이 데이터 변경 가능 즉시 수정
High XSS, SQL인젝션, 권한 우회 즉시 수정
Medium 불필요한 데이터 노출, 로깅 누락 수정 권고
Low 코드 스타일, 예외처리 개선 권고

출력 형식

## 보안 검토 결과

### Critical
- [파일:라인] 문제 설명 → 수정 방법

### High
- ...

### 통과 항목
- ...