Răsfoiți Sursa

fix(security): /api/config 익명 응답에서 SMTP 자격증명 등 민감 Basic 필드 제거

공개(AllowAnonymous) GET /api/config 가 Basic.SmtpPassword/SmtpUsername/SmtpServer +
RootID + Admin/Front IP 화이트리스트를 평문 노출 중이었음 (메일계정 탈취 등 심각).

- Web.Api Config 엔드포인트: 안전 필드만 담은 sanitized Basic 으로 교체(default-deny 화이트리스트).
- Get.Response.Basic 을 set 접근자로 변경(정화 사본 교체용).
- 프론트는 해당 필드 미사용(타입/DEFAULT_CONFIG 에만 존재). Admin/DirectMailService/MailWorker 는
  in-process(mediator/cache/DB)로 원본 접근 → 영향 없음. redis 캐시는 정화 전 전량 직렬화되어 무결.
- 주의: 이미 공개 노출됐던 dev@web.or.kr SMTP 비밀번호는 배포 후 로테이션 필요.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
KIM-JINO5 2 săptămâni în urmă
părinte
comite
d192e9b1d6

+ 2 - 1
Application/Features/Config/Get/Response.cs

@@ -7,7 +7,8 @@ public sealed class Response
 {
     public int ID { get; init; }
 
-    public BasicConfigDto Basic { get; init; } = new();
+    // set: 공개 /api/config 엔드포인트가 민감 필드(SMTP 자격증명·RootID·IP 화이트리스트) 제거용 sanitized 사본으로 교체 가능하도록.
+    public BasicConfigDto Basic { get; set; } = new();
     public ImagesConfigDto Images { get; init; } = new();
     public MetaConfigDto Meta { get; init; } = new();
     public CompanyConfigDto Company { get; init; } = new();

+ 21 - 0
Web.Api/Endpoints/Config/Config.cs

@@ -13,6 +13,27 @@ internal sealed class Config : IEndpoint
         ) => {
             var result = await sender.Send(new Application.Features.Config.Get.Query(), ct);
 
+            // 공개(익명) 응답 정화 — 민감 Basic 필드는 프론트가 쓰지 않으며 노출 시 심각한 보안 위험
+            // (SMTP 자격증명 = 메일계정 탈취, RootID = 최고관리자 ID, IP 화이트리스트 = 인프라 노출).
+            // 화이트리스트 방식(default-deny): 안전 필드만 복사 → 이후 새 Basic 필드도 명시 전엔 비공개.
+            // Admin/DirectMailService/MailWorker 는 in-process(mediator·cache·DB)로 원본을 읽어 영향 없음.
+            if (result is not null)
+            {
+                result.Basic = new Application.Features.Config.Get.Response.BasicConfigDto
+                {
+                    SiteName = result.Basic.SiteName,
+                    SiteURL = result.Basic.SiteURL,
+                    FromEmail = result.Basic.FromEmail,
+                    FromName = result.Basic.FromName,
+                    BlockAlertTitle = result.Basic.BlockAlertTitle,
+                    BlockAlertContent = result.Basic.BlockAlertContent,
+                    IsMaintenance = result.Basic.IsMaintenance,
+                    MaintenanceContent = result.Basic.MaintenanceContent,
+                    NoteDailySendLimit = result.Basic.NoteDailySendLimit
+                    // 비공개(제외): RootID, SmtpServer, SmtpPort, SmtpEnableSSL, SmtpUsername, SmtpPassword, AdminWhiteIPList, FrontWhiteIPList
+                };
+            }
+
             return ApiResponse.Ok(result);
         })
         .WithTags("Config")