From 535fe01624ca1a6b32a6cee7ef696208aeb6ae30 Mon Sep 17 00:00:00 2001 From: John Niang Date: Tue, 12 Aug 2025 12:08:46 +0800 Subject: [PATCH] Check if the contents of comment and reply are valid before persistence (#7677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /milestone 2.21.x #### What this PR does / why we need it: This PR checks if the contents of comment and reply are valid before persistence to prevent users from XSS attacks. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/7675 #### Special notes for your reviewer: Try to comment or reply with the contents from . #### Does this PR introduce a user-facing change? ```release-note 检测评论和回复内容是否合法以防止 XSS 攻击 ``` --- .../content/comment/AbstractCommentService.java | 14 ++++++++++++++ .../app/content/comment/CommentServiceImpl.java | 8 ++++++++ .../halo/app/content/comment/ReplyServiceImpl.java | 8 ++++++++ 3 files changed, 30 insertions(+) diff --git a/application/src/main/java/run/halo/app/content/comment/AbstractCommentService.java b/application/src/main/java/run/halo/app/content/comment/AbstractCommentService.java index 9c054ea39..66f6ee89c 100644 --- a/application/src/main/java/run/halo/app/content/comment/AbstractCommentService.java +++ b/application/src/main/java/run/halo/app/content/comment/AbstractCommentService.java @@ -2,6 +2,9 @@ package run.halo.app.content.comment; import java.util.Set; import lombok.RequiredArgsConstructor; +import org.jsoup.Jsoup; +import org.jsoup.safety.Safelist; +import org.springframework.lang.NonNull; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.util.Assert; import reactor.core.publisher.Mono; @@ -21,6 +24,7 @@ public abstract class AbstractCommentService { protected final ReactiveExtensionClient client; protected final UserService userService; protected final CounterService counterService; + private final Safelist safelist = Safelist.relaxed(); protected Mono fetchCurrentUser() { return ReactiveSecurityContextHolder.getContext() @@ -74,4 +78,14 @@ public abstract class AbstractCommentService { ) .switchIfEmpty(Mono.fromSupplier(CommentStats::empty)); } + + /** + * Check if the given html is a safe HTML. + * + * @param html html content + * @return true if the html is safe, false otherwise + */ + protected boolean isSafeHtml(@NonNull String html) { + return Jsoup.isValid(html, safelist); + } } diff --git a/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java b/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java index d6059ec64..d107d4077 100644 --- a/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java @@ -13,6 +13,7 @@ import org.springframework.data.domain.Sort; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; @@ -68,6 +69,13 @@ public class CommentServiceImpl extends AbstractCommentService implements Commen @Override public Mono create(Comment comment) { + if (comment.getSpec() == null + || comment.getSpec().getContent() == null + || !isSafeHtml(comment.getSpec().getContent())) { + return Mono.error(new ServerWebInputException(""" + The content of comment must not be empty or contains unsafe HTML.\ + """)); + } return environmentFetcher.fetchComment() .flatMap(commentSetting -> { if (Boolean.FALSE.equals(commentSetting.getEnable())) { diff --git a/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java b/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java index 26567274a..e1214943e 100644 --- a/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java @@ -17,6 +17,7 @@ import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; @@ -52,6 +53,13 @@ public class ReplyServiceImpl extends AbstractCommentService implements ReplySer @Override public Mono create(String commentName, Reply reply) { + if (reply.getSpec() == null + || reply.getSpec().getContent() == null + || !isSafeHtml(reply.getSpec().getContent())) { + return Mono.error(new ServerWebInputException(""" + The content of reply must not be empty or contains unsafe HTML.\ + """)); + } return client.get(Comment.class, commentName) .flatMap(this::approveComment) .filter(comment -> isTrue(comment.getSpec().getApproved()))