Skip to content

Commit

Permalink
Merge pull request #103 from JNU-econovation/feature/BE-39
Browse files Browse the repository at this point in the history
[BE-39] XSS Validation
  • Loading branch information
BlackBean99 authored Sep 1, 2023
2 parents c96c20f + 4be921b commit fefe4d4
Show file tree
Hide file tree
Showing 14 changed files with 119 additions and 0 deletions.
Binary file modified server/.gradle/7.6.1/checksums/checksums.lock
Binary file not shown.
Binary file modified server/.gradle/7.6.1/executionHistory/executionHistory.bin
Binary file not shown.
Binary file modified server/.gradle/7.6.1/executionHistory/executionHistory.lock
Binary file not shown.
Binary file modified server/.gradle/7.6.1/fileHashes/fileHashes.bin
Binary file not shown.
Binary file modified server/.gradle/7.6.1/fileHashes/fileHashes.lock
Binary file not shown.
Binary file modified server/.gradle/7.6.1/fileHashes/resourceHashesCache.bin
Binary file not shown.
Binary file modified server/.gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary file not shown.
2 changes: 2 additions & 0 deletions server/Recruit-Api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ dependencies {
implementation 'org.webjars:stomp-websocket:2.3.3'
implementation 'org.webjars:bootstrap:3.3.7'
implementation 'org.webjars:jquery:3.1.1-1'
// XSS validator
implementation 'org.jsoup:jsoup:1.13.1'

implementation 'org.springdoc:springdoc-openapi-ui:1.6.12'
// testImplementation 'org.junit.jupiter:junit-jupiter-api:4.13'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.econovation.recruit.api.applicant.usecase.TimeTableLoadUseCase;
import com.econovation.recruit.api.applicant.usecase.TimeTableRegisterUseCase;
import com.econovation.recruitcommon.annotation.ApiErrorExceptionsExample;
import com.econovation.recruitcommon.annotation.XssProtected;
import com.econovation.recruitdomain.domains.applicant.dto.BlockRequestDto;
import com.econovation.recruitdomain.domains.applicant.dto.TimeTableVo;
import com.econovation.recruitdomain.domains.dto.QuestionRequestDto;
Expand Down Expand Up @@ -42,6 +43,7 @@ public class ApplicantController {

@Operation(summary = "지원자가 지원서를 작성합니다.", description = "반환 값은 생성된 지원자의 ID입니다.")
@ApiErrorExceptionsExample(CreateApplicantExceptionDocs.class)
@XssProtected
@PostMapping("/applicants")
public ResponseEntity registerApplicant(@RequestBody List<BlockRequestDto> blockElements) {
UUID applicantId = applicantRegisterUseCase.execute(blockElements);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.econovation.recruit.utils.aop;

import com.econovation.recruitdomain.domains.applicant.dto.BlockRequestDto;
import com.econovation.recruitinfrastructure.slack.SlackMessageProvider;
import com.econovation.recruitinfrastructure.slack.config.SlackProperties;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;
import org.springframework.stereotype.Component;

@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class XssValidationAspect {
private final SlackMessageProvider slackMessageProvider;
private final SlackProperties slackProperties;

@Around("@annotation(com.econovation.recruitcommon.annotation.XssProtected)")
public Object validateXssInput(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof List) {
validateDtoList((List<?>) arg);
}
}
return joinPoint.proceed();
}

private Boolean validateDtoList(List<?> dtoList) {
for (Object dto : dtoList) {
if (dto instanceof BlockRequestDto) {
Boolean attackSignal = validateBlockRequestDto((BlockRequestDto) dto);
if (attackSignal) {
return true;
}
}
}
return false;
}

private Boolean validateBlockRequestDto(BlockRequestDto dto) {
String sanitizedAnswer = Jsoup.clean(dto.getAnswer(), Whitelist.relaxed());
if (!dto.getAnswer().equals(sanitizedAnswer)) {
slackMessageProvider.sendMessage(
slackProperties.getUrl(), generateApplicantRegisterMessage(dto));
return true;
}
return false;
}

private String generateApplicantRegisterMessage(BlockRequestDto dto) {
String inputAnswer = dto.getAnswer();
String sanitizedAnswer = Jsoup.clean(inputAnswer, Whitelist.relaxed());

if (!inputAnswer.equals(sanitizedAnswer)) {
// XSS 공격이 의심되는 부분을 추출합니다.
String detectedXss = extractDetectedXss(inputAnswer, sanitizedAnswer);

// 경고 메시지 생성
return String.format(
":interrobang::interrobang::interrobang: XSS 공격이 감지되었습니다.:interrobang::interrobang::interrobang:\n\n"
+ ":party_parrot2: 입력값 : %s\n\n"
+ ":party_parrot2: 변환된 공격 추출 값 : %s",
inputAnswer, detectedXss);
}

// 공격이 감지되지 않은 경우
return "No XSS attacks detected.";
}

private String extractDetectedXss(String inputAnswer, String sanitizedAnswer) {
// inputAnswer와 sanitizedAnswer를 비교하여 공격이 감지된 부분을 추출합니다.
StringBuilder detectedXss = new StringBuilder();

for (int i = 0; i < inputAnswer.length(); i++) {
if (i >= sanitizedAnswer.length()
|| inputAnswer.charAt(i) != sanitizedAnswer.charAt(i)) {
detectedXss.append(inputAnswer.charAt(i));
}
}

return detectedXss.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.econovation.recruitcommon.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface XssProtected {}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public enum GlobalErrorCode implements BaseErrorCode {

@ExplainError("500번대 알수없는 오류입니다. 서버 관리자에게 문의 주세요")
INTERNAL_SERVER_ERROR(INTERNAL_SERVER, "GLOBAL_500_1", "서버 오류. 관리자에게 문의 부탁드립니다."),
@ExplainError("XSS 공격이 의심되는 입력이 감지되었습니다. 정상적인 입력값을 넣어주세요.")
XSS_SCRIPT_ATTACK(BAD_REQUEST, "GLOBAL_400_3", "XSS 공격이 의심되는 입력이 감지되었습니다. 정상적인 입력값을 넣어주세요."),

OTHER_SERVER_BAD_REQUEST(BAD_REQUEST, "FEIGN_400_1", "Other server bad request"),
OTHER_SERVER_UNAUTHORIZED(BAD_REQUEST, "FEIGN_400_2", "Other server unauthorized"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.econovation.recruitcommon.exception;

public class XssScriptAttackException extends RecruitCodeException {

public static final RecruitCodeException EXCEPTION = new XssScriptAttackException();

private XssScriptAttackException() {
super(GlobalErrorCode.XSS_SCRIPT_ATTACK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
Expand All @@ -18,6 +19,7 @@ public class SlackMessageProvider {

private final SlackProperties slackProperties;

@Transactional
public void sendMessage(String url, String text) {
// 슬랙 url 이 null 일경우 안보냄.
if (Objects.isNull(url)) return;
Expand Down

0 comments on commit fefe4d4

Please sign in to comment.