Files
Gnuboard7/app/Search/SearchHighlighter.php
T
HeuJung cf1d8ab52c fix(security): 보안 제보 14건 + 유사 결함 전수조사 수정
sir.kr 커뮤니티 제보(Xbuilder 님 외)와 전수조사 발굴분을 코어·게시판·
이커머스·페이지·ckeditor5 전반에 반영.

- 통합 검색 하이라이트: 코어 SearchHighlighter 로 원문 이스케이프 후 강조
 (저장형 XSS 차단, board/ecommerce/page 3중복제 통합)
- 공개 최신글·인기글·관리자 대시보드: 읽기권한 없는 게시판·블라인드·비활성
 게시판 글 필터. 비밀글은 기존 공개 정책대로 제목 노출 유지(본문만 보호)
- 게시판 관리 대시보드 권한 게이트, 이커머스 쿠폰 다운로드·리뷰 대상·주문옵션
 확정 스코프 강화, 취소/환불 항목 검증 회원/비회원 대칭
- 만료 토큰 거부(SetLocale), 회원 탈퇴·정보관리 실패 시 내부 오류 원문 마스킹
- 예외→응답 매핑 정비(generic catch 상태코드), ckeditor 업로드 권한 게이트 정리

정책 정합 정정: 착수 계획이 "비밀글 제목 노출=취약점"으로 전제해 공개 최신글·
대시보드에 넣었던 is_secret 필터는, 저장소가 확정한 "제목 공개, 본문만 보호"
정책(2026-01-02/02-04/06-18/06-19)과 배치되어 되돌림. 읽기권한·블라인드 필터는 유지.
Chrome MCP 실측(T1~T10) + 회귀 테스트로 확인.
2026-08-17 15:46:38 +09:00

63 lines
2.2 KiB
PHP

<?php
namespace App\Search;
/**
* 검색 결과의 키워드 하이라이트와 본문 프리뷰 평문화를 담당하는 공유 헬퍼.
*
* 하이라이트 필드는 소비 측이 HTML 로 렌더하는 계약이므로, 원문을 반드시
* 이스케이프한 뒤 검색어만 <mark> 로 감싼다. 이스케이프를 건너뛰면 제목·본문에
* 삽입된 태그가 그대로 실행 계약으로 나간다(게시판/상품/페이지 검색 공통).
*/
class SearchHighlighter
{
/**
* 원문을 HTML 이스케이프한 뒤 검색어만 <mark> 로 감쌉니다.
*
* @param string|null $text 원문 텍스트(평문)
* @param string $keyword 검색어
* @return string 이스케이프 완료된 안전한 HTML
*/
public static function highlight(?string $text, string $keyword): string
{
if ($text === null || $text === '') {
return '';
}
$safe = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
if ($keyword === '') {
return $safe;
}
// 키워드도 동일하게 이스케이프해, 이스케이프된 원문과 일관되게 매칭한다.
$safeKeyword = htmlspecialchars($keyword, ENT_QUOTES, 'UTF-8');
$escapedKeyword = preg_quote($safeKeyword, '/');
$result = preg_replace('/('.$escapedKeyword.')/iu', '<mark>$1</mark>', $safe);
// 유효하지 않은 UTF-8 등으로 preg_replace 가 null 을 반환하면 이스케이프본을 유지한다.
return $result ?? $safe;
}
/**
* HTML 본문을 태그 없는 평문으로 변환합니다.
*
* 엔티티를 먼저 디코드한 뒤 태그를 제거해, 엔티티로 인코딩된 태그가
* 평문화 단계에서 실제 태그로 부활하지 못하게 합니다.
*
* @param string|null $html 본문(HTML 또는 평문)
* @return string 태그가 제거된 평문
*/
public static function toPlainText(?string $html): string
{
if ($html === null || $html === '') {
return '';
}
$decoded = html_entity_decode($html, ENT_QUOTES, 'UTF-8');
return trim((string) preg_replace('/\s+/', ' ', strip_tags($decoded)));
}
}