비정상적인 Form 제출 이슈

이슈

자사 서비스의 기능 중 데이터 견적 요청을 위한 페이지가 있다. 고객이 해당 페이지에서 정보를 입력 후 제출하면 Form 데이터는 DB에 저장되고 담당자들에게 메일로 발송된다.

DB에 비정상적인 Form 데이터가 있는 것을 발견했다. 라디오 버튼 값이 모두 선택 값으로도 들어오고, 첨부파일 링크와 이메일을 제외한 모든 Input 값이 알 수 없는 영문 조합으로 채워져있었다.

원인

어떤 방법으로 한건지 확실히 파악하지는 못했으나, 수동으로 했던 봇으로 했던 비정상적인 접근을 시도한다는 것만 확인했다.

경과 및 해결

비정상적인 Form 데이터의 이메일 항목은 모두 알 수 없는 영문 조합의 yahoo 이메일인 것을 확인했다. 이후 모니터링을 위해 s3 copy 로직 관련 로그를 추가한 결과, source 파라미터가 없이 (임시 파일 업로드 없이) send 하여 에러가 나는 것을 확인했다.

👩‍🔧 데이터 견적 요청 Form 중에는 첨부파일을 올릴 수 있는데, aws s3 javascript sdk로 클라이언트단에서 업로드가 가능하다. 제출 버튼을 눌러야 Form 데이터를 최종적으로 제출할 수 있고, 그 전까지 업로드하는 파일들은 사용자의 의지에 따라 교체 및 삭제할 수 있기에 업로드 파일들은 s3 bucket의 임시 폴더로 들어간다.

최종적으로 제출 버튼을 누를 땐, 가장 마지막의 업로드된 임시 파일을 upload 폴더로 복사한다. source 파라미터는 임시 폴더 안에 있는 복사할 주체 파일의 주소, destination 파일은 복사할 목적지의 주소이다(s3 copy 로직). 임시 폴더는 스케줄링을 통해 주기적으로 삭제된다.

 

 

또한 s3 에러를 일으킨 이메일과 같은 시간에 들어온 DB 내 비정상적인 데이터의 이메일과 같은 것을 확인했다.

아래 JSON 데이터는 당시 DB에 들어왔던 비정상적인 데이터의 예시이다.

{
"table": "tb_deep_request",
"rows":
[
{
"idx": 22,
"step1_type": "textbox,file",
"step2_type": "textbox,file",
"step1_textbox": "ZRyiPfMnc",
"step2_textbox": "AgwdoHsS",
"step1_file_link": null,
"step2_file_link": null,
"lastname_with_prefix": "QCyaJEYIqpn",
"email": "test@test.com",
"email_2nd": "test@test.com",
"profit_type": "nonprofit,for-profit",
"org_name": "ikZscehMXB",
"messages": "whKoNpHljre",
"is_use": null,
"priority": null,
"created_at": "2022-04-06 02:09:08",
"updated_at": "2022-04-06 02:09:08"
},
{
"idx": 23,
"step1_type": "textbox,file",
"step2_type": "textbox,file",
"step1_textbox": "irshwWCt",
"step2_textbox": "hMjxfgDdPybY",
"step1_file_link": null,
"step2_file_link": null,
"lastname_with_prefix": "xARdKQco",
"email": "test@test.com",
"email_2nd": "test@test.com",
"profit_type": "nonprofit,for-profit",
"org_name": "yFVwDfHBluLZWk",
"messages": "kQDTeila",
"is_use": null,
"priority": null,
"created_at": "2022-04-06 20:28:05",
"updated_at": "2022-04-06 20:28:05"
},
{
"idx": 24,
"step1_type": "textbox,file",
"step2_type": "textbox,file",
"step1_textbox": "yQPuYgvfeSNap",
"step2_textbox": "rsVHmYktJxDhnUSM",
"step1_file_link": null,
"step2_file_link": null,
"lastname_with_prefix": "XxCZvgEGDWuYrdlA",
"email": "test@test.com",
"email_2nd": "test@test.com",
"profit_type": "nonprofit,for-profit",
"org_name": "JnMCtOEhUxVm",
"messages": "rvtpwidzPaQHcU",
"is_use": null,
"priority": null,
"created_at": "2022-04-07 23:32:24",
"updated_at": "2022-04-07 23:32:24"
}
]
}

확인해보면, step1_type, step2_type 항목은 라디오 버튼으로 하나만 선택되어야 하는데 모든 값이 선택되어있고, textbox 항목도 알 수 없는 영문조합으로 모두 삽입되어있다. file link는 step_type 항목에서 선택되어있음에도 불구하고 파일 주소는 null 이다. 첨부파일 업로드 까지는 비정상적으로 접근하지 못한 것으로 추측한다.

 

첫번째 해결방법으로 Spring Securiy의 csrf 설정을 추가해주었다.

import java.util.Arrays;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(HttpSecurity security) throws Exception {
		security.csrf()
			.ignoringAntMatchers("/api/test/**")
			.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
		
		security.httpBasic().disable();
    }
}
<meta id="_csrf" name="_csrf" content="${_csrf.token}"/>
<meta id="_csrf_header" name="_csrf_header" content="${_csrf.headerName}"/>
.
.
.
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

csrf 토큰의 저장소는 쿠키로 설정하고, httpOnly 옵션을 false로 설정하여 javascript에서 접근 가능토록 한다. 이메일이 실제 존재하는 지 검증하는 ajax 로직에서 csrf 토큰을 함께 전송하기 위해 토큰에javascript로 접근해야하기 때문이다.

      var token = $("meta[name='_csrf']").attr("content");
      var header = $("meta[name='_csrf_header']").attr("content");
      
      var email = $('#email_'+str).val().trim(); 
      
      $.ajax({
          type: "GET",
          url: "/api/test",
          data: {"email": email},
          beforeSend : function(xhr) {
              xhr.setRequestHeader(header, token);
          },
          success: function (ret) {

          },
          error: function (e) {

      });

하지만 csrf 설정에도 비정상적인 Form 제출은 계속 이루어졌고, 두번째 해결방법으로 컨트롤러 단에서 모든 라디오 버튼 값들이 삽입되어 있을 시, 차단하도록 하여 비정상적인 데이터 삽입은 막을 수 있었다.