mirror of https://github.com/halo-dev/halo
Check if the contents of comment and reply are valid before persistence (#7677)
#### 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 <https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html>. #### Does this PR introduce a user-facing change? ```release-note 检测评论和回复内容是否合法以防止 XSS 攻击 ```pull/7681/head
parent
59030f839a
commit
535fe01624
|
@ -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<User> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Comment> 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())) {
|
||||
|
|
|
@ -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<Reply> 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()))
|
||||
|
|
Loading…
Reference in New Issue