Rhymix 애드온 lua_external_file을 참고만 하고 모듈로 전면 재설계 — 첨부파일을 로컬 마운트/S3 호환 스토리지/FTP로 자동 이동시켜 서버 용량을 절감
# 접두어로 즉시 일시중지 기능 추가.files 테이블 행을 즉시 지우지만, 원격(S3/FTP) 삭제가 실패해 delete_failed로 매핑이 남아있는 파일은 권한 체크 로직이 "레코드 없음 = 확인 불필요 = 허용"으로 잘못 판단해 삭제된 게시글의 첨부파일이 URL만 알면 계속 열람 가능했음. files 행이 없으면 항상 차단하도록 수정. 같은 점검에서 배지/안내문 카운트가 1000건을 넘으면 실제보다 적게 표시되던 부정확도 함께 수정.catch(\Exception)로만 감싸여 있어 PHP의 Error/TypeError 계열은 놓치고 있던 것을 catch(\Throwable)로 방어 범위 확장. 점검 중 v1.7.2에서 고친 시나리오가 getMapByOriginalPath 쿼리의 status='migrated' 필터 덕에 애초에 그 경로로는 도달하지 않는다는 것도 재확인(그래도 "트리거가 끝까지 못 돌아 상태 갱신이 누락된" 케이스에는 여전히 유효한 방어). buildRemotePath()의 ltrim() 마스크 사용 방식도 더 정확한 방식으로 교체(현재 데이터로는 문제 된 적 없는 사전 예방적 수정).streamFile()/downloadToLocal()가 strpos($real, $realStorage) !== 0로만 경로 포함 여부를 확인하고 있어, $realStorage가 /data/storage일 때 /data/storage_evil/x 같은 형제 디렉터리도 prefix가 겹쳐 통과할 수 있는 구조였음. 현재는 $remotePath가 사용자 직접 입력이 아니라 매핑 테이블 값이라 실사용 위험은 낮지만, 향후 재사용 대비 사전 방어로 구분자 포함 비교 방식으로 교체. CLI로 정상 접근/traversal 차단 양쪽 다 직접 검증.
streamFile()/downloadToLocal()가 realpath() 결과가 저장소 경로 안에 있는지 strpos($real, $realStorage) !== 0로만 확인 — $realStorage가 /data/storage일 때 /data/storage_evil/x 같은 형제 디렉터리도 문자열 prefix가 겹쳐 통과하는 고전적인 우회 패턴이었음.$remotePath는 사용자가 직접 넘기는 값이 아니라 매핑 테이블에 기록된 값이라 실제 악용 가능성은 낮으나, 향후 이 드라이버가 다른 입력 경로에 재사용될 경우를 대비해 strpos($real, $realStorage . DIRECTORY_SEPARATOR) !== 0 && $real !== $realStorage 방식으로 교체.catch(\Exception $e)로만 감싸여 있었음. PHP 7+의 TypeError/Error 계열은 \Exception이 아니라 \Throwable이라 이 catch에 걸리지 않아, 드라이버 쪽 Error 발생 시 트리거가 도중에 끊기고(삭제 트리거는 delete_failed 기록도 못 남긴 채) 요청 전체가 죽을 수 있었음. catch(\Throwable $e)로 전부 교체.delete_failed 시나리오는 getMapByOriginalPath 쿼리가 status='migrated'만 조회하도록 이미 필터링돼 있어 그 경로로는 도달하지 않음을 재확인 — 이번 예외 처리 강화와 결합했을 때 여전히 유효한 방어(트리거가 끝까지 못 돌아 상태 갱신이 누락되는 "유령 매핑" 케이스).buildRemotePath()의 ltrim($str, './')는 "./" 리터럴이 아니라 문자 마스크로 동작해 과도하게 잘릴 여지가 있었음(현재 실제 경로 형식에서는 문제 된 적 없음) — substr() 기반 리터럴 접두어 제거로 교체.pickLocalStorageRoot()가 "여유 공간 확보량(GB)"을 항상 지켜서 그 밑으로는 저장하지 않음. 그런데 "데이터 이전 및 통합"으로 로컬 경로를 목표로 고르면 이 확인 없이 그냥 밀어넣고 있었음 — 반복 실행 시 백업 등을 위해 남겨두려던 예약 공간까지 채울 수 있었음.files 테이블 행을 즉시 삭제(action="delete" 쿼리 직접 확인)하지만, 원격(S3/FTP) 삭제가 실패해 delete_failed 상태로 매핑이 남아있으면 정적 서빙 폴백의 isServeFilePermitted()가 "파일 레코드를 못 찾음 → 권한 체크 대상 아님 → 허용"으로 잘못 처리하고 있었음. 결과적으로 게시글이 삭제됐어도 원격 삭제만 실패한 첨부파일은 URL을 아는 사람에게 계속 노출되는 상태였음.files 행이 없으면 이제 항상 차단하도록 수정 — 관리자가 매핑 목록에서 "삭제 재시도"로 원격 삭제를 완료하기 전까지는 아무에게도 서빙되지 않음.getLegacyFileCount())로 교체.getLegacyFileCount() 추가.s3_2_*/s3_3_* 등 새 접두어 필드 사용.<details>) 섹션 제공.downloadToLocal() 추가 (S3=인증 GET, FTP=ftp_get(), 로컬=파일 복사).bsplus_external_storage_board 추가 (관리자 화면에서 "설치" 버튼 클릭 필요했던 변경).isDownloadable(), 게시글/댓글 읽기 권한)를 완전히 우회함을 관리자 화면에 명시. 완전 공개 게시판에서만 권장.triggerCheckServeFile)에도 추가.S3Driver가 관련 설정을 아예 읽지 않고 있었음. 실제로 반영되도록 수정.attemptChainUpload()).storage_type 설정은 하위호환으로 1순위 자동 승격.local_path는 하위호환.disk_free_space()로 여유 공간 확인 → 부족하면 자동으로 다음 순위 경로 사용. 전부 꽉 차면 failed 상태로 사전 감지.storage_root 컬럼 추가 — 파일별 실제 저장 경로 기록.# 접두어로 경로를 즉시 일시중지하는 기능 추가.StorageDriverFactory::create()에 저장소 종류를 명시적으로 넘기고, 각 트리거는 파일별 기록된 storage_type을 사용하도록 수정.isDownloadable() + 게시글/댓글 읽기 권한 체크, 경로 순회 방지.printNavigation() 미존재, foreach(range()) 템플릿 컴파일 오류, class="admin" 오용, 다운로드 버튼 원격 파일 미지원, 관리자 탭 미연결, FTP 확장 미설치 가드.| PHP | 7.4 이상 (Rhymix 공식 최소 요구사항), 8.2 이상 권장. 상한선 없음. |
|---|---|
| Rhymix | 2.0.0 이상 권장. 실제 설치·테스트는 2.1.35에서 진행. |
| 라이선스 | GPLv2 (Rhymix 코어 및 다른 BSplus 모듈과 동일) |