1. 개요

이전 글에서는 직접 개발한 Spring Boot 웹 애플리케이션을 대상으로 Docker 기반 보안 진단 환경을 구축했다.

이번에는 구축한 게시판에 Burp Suite를 연결하여 HTTP 요청과 응답을 분석하고, 게시글 수정 기능에서 IDOR(Insecure Direct Object Reference) 취약점이 발생하는지 확인하고자 한다.

 

IDOR는 서버에서 사용자가 요청한 객체에 대한 접근 권한을 적절하게 검증하지 않을 때 발생할 수 있는 접근통제 취약점이다.

예를 들어 일반 사용자 A가 다른 사용자 B의 게시글 ID를 지정하여 수정 요청을 보냈을 때, 서버가 게시글 소유권을 확인하지 않고 수정한다면 접근통제 취약점에 해당할 수 있다.

 

이번 분석에서는 정상 사용자와 다른 사용자의 게시글 수정 요청을 비교하고, 실제 데이터베이스의 변경 여부까지 확인했다.

진단 환경

구분
환경
분석 대상 자체 개발 Spring Boot 게시판
서버 환경 Docker, Spring Boot 3.3.11
데이터베이스 MySQL 8.0
분석 도구 Burp Suite Community Edition
분석 기법 HTTP 요청 변조 및 소스코드 분석
진단 항목 게시글 수정 기능의 접근통제(IDOR)
대상 URL http://localhost:8080

진단에는 로컬 환경에서 생성한 테스트 계정 두 개를 사용했다.

  • jcob: 정상 수정 요청 및 타인 게시글 수정 시도
  • jcob2: 타인 게시글 수정 시도의 대상 계정

본 실습은 직접 구축한 로컬 테스트 환경에서 진행했다.


2. Burp Suite 환경 구성 및 HTTP 요청 분석

2.1 Burp Suite 내장 브라우저 연결

Burp Suite를 실행한 후 다음 순서로 진행했다.

  1. Proxy → Intercept 메뉴 이동
  2. Intercept off 설정
  3. Open Browser 클릭
  4. 내장 브라우저에서 게시판 접속
http://localhost:8080

Intercept off는 요청을 차단하지 않고 서버로 전달하면서 HTTP History에 기록하는 상태다.

이를 통해 정상적인 브라우저 동작을 유지하면서 HTTP 요청과 응답을 분석할 수 있다.

2.2 HTTP 요청 및 응답 확인

게시판에 접속하면 다음과 같은 요청이 발생한다.

GET / HTTP/1.1
Host: localhost:8080

 

 

서버에서는 정상적으로 응답했다.

HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
X-Frame-Options: DENY

 

주요 응답 헤더는 다음과 같다.

헤더
의미
X-Content-Type-Options 브라우저의 MIME 타입 추측 제한
X-XSS-Protection 구형 브라우저의 XSS 필터 관련 설정
Cache-Control 응답 데이터 캐싱 정책
X-Frame-Options iframe 삽입 제한

X-XSS-Protection: 0은 구형 XSS 필터를 비활성화하는 설정으로, 해당 값만으로 XSS 취약점이 존재한다고 판단할 수 없다.

 

또한 로그인 과정에서 JSESSIONID 쿠키가 사용되는 것을 확인했다.

Cookie: JSESSIONID=[REDACTED]

 

JSESSIONID는 Spring Security의 세션 기반 인증에서 사용되는 세션 식별자다.

이후 Repeater에서 인증된 요청을 재전송할 때 사용되므로, 요청에 포함된 세션 정보가 유효한지 확인해야 한다.


3. 게시글 수정 기능 접근통제(IDOR) 검증

3.1 정상적인 수정 요청 확인

먼저 jcob 계정으로 로그인한 상태에서 본인이 작성한 33번 게시글의 수정 기능을 확인했다.

 

Burp Suite의 HTTP History에서는 다음 두 요청이 기록됐다.

GET /articles/33/form HTTP/1.1
POST /articles/33/form HTTP/1.1

각 요청의 역할은 다음과 같다.

HTTP Method
URL 기능
GET /articles/33/form 게시글 수정 페이지 조회
POST /articles/33/form 게시글 수정 내용 저장

 

GET 요청은 수정 화면을 조회하는 용도이고, POST 요청은 입력된 수정 내용을 서버에 전달하는 용도다.

따라서 이번 접근통제 진단에서는 POST 요청을 주요 분석 대상으로 선정했다.

3.2 POST 요청 구조 확인

게시글 수정 요청을 확인한 결과 다음과 같은 구조였다.

POST /articles/33/form HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=[REDACTED]

_csrf=[REDACTED]&title=Original-Title&content=Original-Content&hashtag=%23orange

 

주요 파라미터는 다음과 같다.

파라미터
설명
articleId URL 경로에 포함된 수정 대상 게시글 ID
_csrf CSRF 방어를 위한 토큰
title 게시글 제목
content 게시글 내용
hashtag 게시글 해시태그
JSESSIONID 로그인 세션 식별자

특히 URL의 게시글 ID는 클라이언트가 변경할 수 있는 값이므로, 서버 측에서 해당 게시글에 대한 수정 권한을 반드시 확인해야 한다.


4. Burp Repeater를 이용한 정상 수정 요청 검증

4.1 Repeater로 요청 전송

HTTP History에서 다음 요청을 선택했다.

POST /articles/33/form

마우스 오른쪽 버튼을 클릭한 후 Send to Repeater를 선택했다.

 

Burp Repeater는 HTTP 요청을 직접 수정하고 동일한 서버로 반복 전송할 수 있는 도구다.

Repeater에서는 HTTP 요청 원문을 수정하거나 Inspector에서 개별 파라미터 값을 수정할 수 있다.

 

이번 실습에서는 Inspector의 Request body parameters를 이용했다.

4.2 요청 파라미터 수정

정상적인 게시글 수정이 수행되는지 확인하기 위해 제목과 내용에 식별 가능한 테스트 문자열을 입력했다.

title=IDOR-TEST-001
content=IDOR-CONTENT-001

 

이때 로그인 세션과 CSRF 토큰은 그대로 유지했다.

수정한 요청은 기존과 동일하게 다음 엔드포인트로 전송했다.

POST /articles/33/form HTTP/1.1

 

응답은 다음과 같은 형태로 반환됐다.

HTTP/1.1 302 Found
Location: http://localhost:8080/articles/33

 

이는 서버가 요청 처리 후 게시글 상세 페이지로 리다이렉트하도록 응답했다는 의미다.

단, 302 Found 응답만으로 게시글 수정 성공 여부를 확정할 수는 없다.

4.3 실제 수정 결과 확인

Burp Suite에서 요청을 전송한 후 브라우저에서 33번 게시글을 조회했다.

GET /articles/33 HTTP/1.1

 

확인 결과 게시글 제목이 다음과 같이 변경됐다.

IDOR-TEST-001

이를 통해 jcob 계정에서 본인이 작성한 게시글에 대한 수정 요청이 정상적으로 처리되는 것을 확인했다.


5. 타인 게시글 수정 권한 검증

5.1 테스트 시나리오

정상적인 게시글 수정이 가능하다는 것을 확인한 후, 다른 사용자가 작성한 게시글을 수정할 수 있는지 검증했다.

이번 테스트에서는 다음 조건을 사용했다.

항목
테스트 조건
로그인 계정 jcob
정상 게시글 33번 (jcob 소유)
수정 대상 게시글 75번 (jcob2 소유)
진단 유형 타인 게시글 수정 권한 우회
예상 결과 권한 없는 수정 요청 차단

즉, jcob으로 로그인한 상태에서 jcob2의 게시글을 수정하려는 시나리오다.

 

[jcob2 계정의 75번 게시글]

5.2 게시글 ID 및 요청 데이터 변조

정상적으로 처리됐던 수정 요청을 기준으로 URL의 게시글 ID를 변경했다.

변경 전:

POST /articles/33/form HTTP/1.1

변경 후:

POST /articles/75/form HTTP/1.1

 

 

 

수정 여부를 명확하게 구분하기 위해 요청 본문에는 다음 테스트 값을 사용했다.

title=IDOR-TEST-002
content=Unauthorized-Modification-Test

 

이때 로그인 계정은 jcob으로 유지했다.

실제 진단에서는 다른 사용자로 로그인하는 것이 아니라, 현재 사용자의 인증정보를 유지한 상태에서 접근 대상 객체 ID만 변경하는 것이 중요하다.

5.3 서버 응답 확인

변조한 요청을 Repeater에서 전송했다.

서버는 다음과 같이 응답했다.

HTTP/1.1 302 Found
Location: http://localhost:8080/articles/75

응답은 33번 게시글을 정상 수정했을 때와 유사하게 302 Found로 반환됐다.

 

하지만 이것만으로 수정이 성공했는지, 권한검사에 의해 차단됐는지 판단할 수 없었다.

따라서 데이터베이스에서 실제 수정 여부를 확인했다.


6. MySQL을 통한 데이터 변경 여부 확인

6.1 MySQL 접속

Docker에서 실행 중인 MySQL 컨테이너에 접속했다.

docker exec -it selfsupport-mysql mysql -u example_user -p board

 

명령어 구성은 다음과 같다.

명령어
의미
docker exec 실행 중인 컨테이너 내부에서 명령어 실행
-it 대화형 터미널 사용
selfsupport-mysql MySQL 컨테이너 이름
mysql MySQL 클라이언트 실행
-u example_user 데이터베이스 사용자 지정
-p 비밀번호 입력
board 조회할 데이터베이스 지정

6.2 게시글 조회

다음 SQL을 실행하여 75번 게시글의 정보를 확인했다.

SELECT id, user_id, title, content
FROM article
WHERE id = 75;

SQL 구성은 다음과 같다.

  • SELECT: 조회할 컬럼 지정
  • FROM article: 게시글 테이블 조회
  • WHERE id = 75: 75번 게시글만 조회

확인 결과 다음과 같았다.

id      : 75
user_id : jcob2
title   : Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.
content : Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem.

게시글 내용 역시 기존 데이터가 유지되었다.

즉, Repeater에서 변조한 수정 요청을 전송했지만 데이터베이스의 제목과 내용은 변경되지 않았다.

6.3 진단 결과

검증 항목
결과
본인 게시글 수정 성공
타인 게시글 수정 요청 전송 가능
타인 게시글 실제 변경 실패
접근통제 여부 소유권 검증 동작 확인
진단 판정 양호 (해당 수정 기능 기준)

이번 테스트에서는 타인 게시글 수정으로 이어지는 IDOR 취약점이 재현되지 않았다.


7. 소스코드 정적 분석

동적 분석에서 타인 게시글 수정이 차단된 원인을 확인하기 위해 Controller와 Service 코드를 분석했다.

7.1 ArticleController 분석

게시글 수정 요청을 처리하는 Controller는 다음과 같다.

@PostMapping("/{articleId}/form")
public String updateArticle(
        @PathVariable Long articleId,
        @AuthenticationPrincipal BoardPrincipal boardPrincipal,
        ArticleRequest articleRequest
) {
    articleService.updateArticle(
            articleId,
            articleRequest.toDto(boardPrincipal.toDto())
    );

    return "redirect:/articles/" + articleId;
}

 

주요 부분은 다음과 같다.

@AuthenticationPrincipal BoardPrincipal boardPrincipal

Spring Security에서 현재 로그인한 사용자의 인증정보를 전달받는다.

 

그리고 다음 메서드를 통해 인증정보를 DTO로 변환한다.

articleRequest.toDto(boardPrincipal.toDto())

즉, 게시글 수정 시 사용자 식별정보를 단순히 클라이언트 요청 파라미터에 의존하지 않고, 인증된 사용자의 정보를 이용하도록 구현되어 있다.

 

이 구조는 사용자가 임의의 사용자 ID를 요청 본문에 넣어 다른 사용자로 가장하는 공격을 방지하는 데 중요하다.

7.2 ArticleService 분석

실제 게시글 수정 로직은 다음과 같다.

public void updateArticle(Long articleId, ArticleDto dto) {
    try {
        Article article = articleRepository.getReferenceById(articleId);
        UserAccount userAccount = userAccountRepository.getReferenceById(
                dto.userAccountDto().userId()
        );

        if (article.getUserAccount().equals(userAccount)) {
            if (dto.title() != null) {
                article.setTitle(dto.title());
            }
            if (dto.content() != null) {
                article.setContent(dto.content());
            }
            article.setHashtag(dto.hashtag());
        }
    } catch (EntityNotFoundException e) {
        log.warn("게시글 수정 실패 - {}", e.getLocalizedMessage());
    }
}

 

 

핵심적인 접근통제 검증 로직은 다음과 같다.

if (article.getUserAccount().equals(userAccount)) {

 

해당 조건에서는 게시글 작성자와 현재 인증된 사용자가 동일한 경우에만 수정 로직을 실행한다.

따라서 jcob이 jcob2의 게시글인 75번에 대해 수정 요청을 전송하더라도 소유권 비교를 통과하지 못하면 실제 데이터 변경이 이루어지지 않는다.

이번 동적 분석 결과 역시 이러한 코드 흐름과 일치했다.

7.3 개선이 필요한 부분

접근통제 자체는 동작했지만 개선할 부분도 확인했다.

 

현재 Service에서는 권한이 없는 경우에도 명시적인 예외를 발생시키지 않고 메서드를 종료한다.

Controller는 Service의 처리 결과와 관계없이 다음 응답을 반환한다.

return "redirect:/articles/" + articleId;

 

따라서 권한이 없는 사용자에게도 정상적인 수정 요청과 동일하게 302 Found 응답이 반환된다.

이 방식은 실제 수정이 차단되더라도 클라이언트가 수정 성공 여부를 명확하게 구분하기 어렵다는 문제가 있다.

 

개선 방향은 다음과 같다.

  • 게시글 소유자와 인증 사용자 정보 불일치 시 명시적 예외 처리
  • 권한 없는 수정 요청에 대해 403 Forbidden 반환
  • 접근통제 위반에 대한 적절한 보안 로그 기록
  • 정상 사용자와 비인가 사용자를 구분하는 자동화 테스트 추가

다만 이번 실습에서는 기존 소스코드를 수정하지 않고 동적·정적 분석을 통해 접근통제 동작을 확인하는 단계까지만 진행했다.


8. 게시글 삭제 기능 접근통제(IDOR) 검증

게시글 수정 기능에 이어 삭제 기능에서도 다른 사용자의 게시글을 임의로 삭제할 수 있는지 확인했다.

8.1 삭제 요청 분석 및 변조

먼저 jcob 계정으로 로그인한 상태에서 본인이 작성한 게시글과 해당 게시글의 삭제 요청을 Burp Suite Proxy에서 확인했다.

 

이후 해당 요청을 Repeater로 전달하고, URL에 포함된 게시글 ID를 다른 사용자(jcob2)가 작성한 75번 게시글로 변경했다.

 

변경 전 — 본인 게시글 삭제 요청

POST /articles/{originalArticleId}/delete HTTP/1.1
Host: localhost:8080
Cookie: JSESSIONID=[REDACTED]

변경 후 — 타인 게시글 삭제 요청

POST /articles/75/delete HTTP/1.1
Host: localhost:8080
Cookie: JSESSIONID=[REDACTED]

로그인 계정과 인증정보는 그대로 유지한 상태에서 삭제 대상 게시글 ID만 변경하여 요청을 전송했다.

8.2 삭제 결과 검증

변조한 삭제 요청을 전송한 후 MySQL에서 해당 게시글의 존재 여부를 확인했다.

SELECT id, user_id, title, content
FROM article
WHERE id = 75;

조회 결과 75번 게시글이 그대로 존재했으며, 삭제되지 않은 것을 확인했다.

 

8.3 소스코드 분석

동적 분석에서 게시글 삭제가 차단된 원인을 확인하기 위해 Controller와 Service의 삭제 처리 로직을 분석했다.

ArticleController.java

@PostMapping("/{articleId}/delete")
public String deleteArticle(
        @PathVariable Long articleId,
        @AuthenticationPrincipal BoardPrincipal boardPrincipal
) {
    articleService.deleteArticle(articleId, boardPrincipal.getUsername());

    return "redirect:/articles";
}

Controller에서는 @AuthenticationPrincipal을 통해 현재 로그인한 사용자의 인증정보를 가져온다.

이후 게시글 ID와 인증된 사용자의 ID를 Service 계층으로 전달한다.

articleService.deleteArticle(articleId, boardPrincipal.getUsername());

 

ArticleService.java

public void deleteArticle(long articleId, String userId) {
    articleRepository.deleteByIdAndUserAccount_UserId(articleId, userId);
}

게시글 삭제는 다음 Repository 메서드를 통해 이루어진다.

articleRepository.deleteByIdAndUserAccount_UserId(articleId, userId);

Spring Data JPA의 메서드 이름 기반 쿼리 생성 방식을 사용하고 있으며, 게시글 ID와 작성자 ID를 조건으로 삭제 대상을 지정한다.

 

개념적으로 다음과 같은 조건에 해당한다.

DELETE FROM article
WHERE id = :articleId
  AND user_id = :userId;

※ 위 SQL은 동작 원리를 설명하기 위한 예시이며, 실제 Hibernate가 생성한 SQL은 별도 확인이 필요

 

즉, 삭제하려는 게시글 ID가 일치하더라도 해당 게시글의 작성자 ID와 로그인한 사용자 ID가 일치하지 않으면 삭제 대상에 포함되지 않는다.

 

이번 테스트에서는 jcob 계정으로 jcob2의 게시글인 75번에 삭제 요청을 전송했지만, 두 사용자의 ID가 일치하지 않아 삭제가 이루어지지 않은 것으로 판단된다.

 

수정 기능과 삭제 기능의 차이

수정 기능에서는 Service 계층에서 게시글 작성자와 인증 사용자를 직접 비교하는 방식으로 접근통제를 수행했다.

반면 삭제 기능에서는 Repository 메서드의 조회·삭제 조건에 작성자 ID를 포함하여 접근통제를 수행한다.

 

두 기능은 구현 방식에 차이가 있지만, 모두 인증된 사용자의 정보를 기준으로 타인 게시글의 비인가 변경을 방지하고 있었다.

다만 삭제 기능에서도 권한이 없는 요청에 대한 명시적인 예외 처리 없이 목록 페이지로 리다이렉트하는 구조이므로, 삭제 권한이 없는 경우 적절한 오류 응답을 제공하도록 개선할 수 있다.


9. 진단 결과 종합

이번 실습에서는 Burp Suite를 활용하여 게시글 수정 및 삭제 기능의 접근통제가 정상적으로 동작하는지 확인했다.

WEB-001 — 타인 게시글 수정 권한 검증

항목
내용
진단 대상 게시글 수정 기능
진단 URL POST /articles/{articleId}/form
진단 유형 IDOR / 접근통제 검증
테스트 사용자 jcob
대상 게시글 75번 (jcob2 소유)
진단 방법 인증정보 유지 후 게시글 ID 변경
서버 응답 302 Found
DB 변경 여부 변경되지 않음
최종 판정 양호 (해당 수정 경로 기준)

 

진단 결론

인증된 사용자가 다른 사용자의 게시글 ID를 지정하여 수정 요청을 전송했으나, 게시글의 제목 및 내용이 변경되지 않는 것을 확인했다.

소스코드 분석 결과 게시글 작성자와 인증된 사용자 정보를 비교하는 소유권 검증 로직이 존재했으며, 해당 로직이 이번 접근통제 테스트에서 정상적으로 동작한 것으로 판단된다.

단, 권한 없는 수정 요청에 대해 명시적인 오류를 반환하지 않고 302 Found로 리다이렉트하는 부분은 개선이 필요하다.

WEB-002 — 타인 게시글 삭제 권한 검증

항목 내용
진단 대상 게시글 삭제 기능
진단 URL POST /articles/{articleId}/delete
진단 유형 IDOR / 접근통제 검증
테스트 사용자 jcob
대상 게시글 75번 (jcob2 소유)
진단 방법 인증정보 유지 후 게시글 ID 변경
DB 삭제 여부 삭제되지 않음
최종 판정 양호 (해당 삭제 경로 기준)

 

진단 결론

인증된 사용자가 다른 사용자의 게시글 ID를 지정하여 삭제 요청을 전송했으나, 데이터베이스에서 해당 게시글이 삭제되지 않은 것을 확인했다.

소스코드 분석 결과 게시글 삭제 시 게시글 ID뿐 아니라 인증된 사용자의 ID를 함께 조건으로 사용하는 로직이 존재했다.

따라서 이번 테스트에서는 타인 게시글 삭제로 이어지는 IDOR 취약점이 재현되지 않았다.

다만 삭제 권한이 없는 요청에 대한 명시적인 예외 처리는 개선이 필요하다.

 

종합 판정

이번에 테스트한 게시글 수정 및 삭제 기능에서는 모두 다른 사용자의 게시글에 대한 비인가 변경이 차단되는 것을 확인했다.

해당 결과는 테스트한 요청 경로를 기준으로 한 판정이며, 게시판의 모든 기능에 대한 접근통제가 안전하다는 의미는 아니다.


10. 마무리 및 향후 분석 계획

이번 진단에서는 Docker로 구축한 Spring Boot 게시판에 Burp Suite를 연결하고, 게시글 수정 및 삭제 요청을 직접 변조하여 접근통제 동작을 검증했다.

특히 서버가 반환하는 HTTP 상태 코드만으로 성공 여부를 판단하지 않고, 실제 데이터베이스의 변경 결과와 Controller·Service 소스코드를 함께 확인했다.

 

이를 통해 다음 내용을 확인할 수 있었다.

  • Burp Suite를 이용한 HTTP 요청·응답 분석
  • Repeater를 이용한 게시글 ID 변조
  • 정상 사용자와 비인가 사용자의 요청 비교
  • MySQL을 통한 실제 데이터 변경 여부 검증
  • Spring Security 인증정보와 서버 측 소유권 검증 구조 분석
  • Service 및 Repository 계층에서의 접근통제 구현 방식 비교

이번에는 게시글 수정 및 삭제 기능에서 IDOR 취약점이 발견되지 않았지만, 접근통제 검증 로직이 실제로 어떤 방식으로 동작하는지 확인했다는 점에서 의미가 있었다.

다음 단계에서는 댓글 기능의 접근통제를 검증하고, 이후 XSS 등 입력값 처리 취약점으로 분석 범위를 확대할 예정이다.

1. 프로젝트 개요

이번 프로젝트는 과거에 직접 개발했던 Spring Boot 기반 자립청년 지원 웹 애플리케이션을 대상으로 웹 보안 취약점을 분석하고, 발견된 취약점을 검증 및 개선하는 것을 목표로 한다.

 

기존 프로젝트는 게시판 기능과 결제·후원 시스템으로 구성되어 있으며, Java와 Spring Boot 기반의 백엔드 개발을 학습하면서 구현했던 프로젝트다.

 

이번에는 기존 소스코드를 활용해 실제 웹 애플리케이션의 동작을 분석하고, 소스코드 정적 분석과 Burp Suite를 이용한 동적 분석을 병행할 계획이다.

 

먼저 비교적 구조가 단순한 게시판 기능을 첫 번째 분석 대상으로 선정했다.

프로젝트 정보

구분 내용
프로젝트명 Selfsupport Site (자립청년 지원 웹 애플리케이션)
개발 언어 Java 17
프레임워크 Spring Boot 3.3.11
보안 프레임워크 Spring Security
ORM Spring Data JPA
템플릿 엔진 Thymeleaf
데이터베이스 MySQL 8.0
빌드 도구 Gradle
컨테이너 환경 Docker Desktop, Docker Compose
진단 도구 Burp Suite (향후 활용)

 

GitHub: https://github.com/yjin0034/selfsupport-site

보안 분석 목표

이번 프로젝트에서 계획한 주요 분석 항목은 다음과 같다.

  • 인증 및 접근통제 취약점
  • 게시글·댓글의 소유권 검증
  • XSS(Cross-Site Scripting)
  • CSRF(Cross-Site Request Forgery)
  • SQL Injection 및 입력값 처리
  • 세션 관리 및 보안 설정

취약점 후보를 발견하면 실제 HTTP 요청과 응답을 통해 재현 가능성을 검증하고, 발생 원인을 소스코드 수준에서 분석할 예정이다.

이후 취약점 개선과 재검증 결과까지 정리하여 웹 모의해킹 포트폴리오로 활용하고자 한다.


2. Docker 환경 구축 목적

기존 프로젝트는 로컬 개발 환경에서 실행하도록 구성되어 있었다.

일반적으로 Spring Boot 애플리케이션을 실행하려면 Java, MySQL 등의 실행 환경을 준비하고 데이터베이스 연결 설정을 구성해야 한다.

그러나 다양한 오픈소스와 여러 버전의 애플리케이션을 분석할 계획이기 때문에, 각 프로젝트의 실행 환경을 분리하고 재현할 수 있는 Docker를 활용하기로 했다.

Docker는 애플리케이션과 실행에 필요한 환경을 컨테이너 단위로 관리할 수 있도록 지원한다.

이를 통해 다음과 같은 장점을 얻을 수 있다.

  • 호스트 운영체제와 애플리케이션 실행 환경 분리
  • Java 및 MySQL 버전 관리
  • 테스트 환경의 반복적인 구축과 재현
  • 여러 오픈소스 프로젝트의 실행 환경 관리
  • 보안 진단 및 PoC 재현 환경 구성

이번에는 Spring Boot 애플리케이션 컨테이너와 MySQL 컨테이너를 분리하여 구성하였다.

환경 구성

Windows
│
├── Docker Desktop (WSL 2)
│   │
│   ├── Spring Boot Container
│   │   └── Board Application
│   │       └── Port 8080
│   │
│   └── MySQL Container
│       └── Database: board
│           └── Port 3306
│
└── Browser / Burp Suite
    └── http://localhost:8080

두 컨테이너는 Docker Compose 네트워크를 통해 서로 통신하도록 구성했다.


3. Docker Desktop 및 WSL 2 설치

3.1 Docker Desktop 설치

Windows 환경에서 Docker를 사용하기 위해 Docker Desktop을 설치했다.

공식 사이트: https://www.docker.com/products/docker-desktop/

설치 과정에서는 현재 Windows 사용자 계정에 설치하는 Per-user installation 방식을 선택했다.

Docker Desktop은 Windows에서 Linux 컨테이너를 실행하기 위해 WSL 2 기반 실행 환경을 사용할 수 있다.

3.2 WSL 2 설치

최초 Docker 설치 및 실행 과정에서 Windows의 가상화 관련 구성 요소가 준비되지 않아 오류가 발생했다.

먼저 Windows 기능에서 다음 항목을 활성화했다.

  • 가상 머신 플랫폼 (Virtual Machine Platform)
  • Linux용 Windows 하위 시스템 (Windows Subsystem for Linux)

이후 관리자 권한으로 PowerShell을 실행하여 WSL을 설치했다.

wsl --install

설치 후 Windows를 재부팅하고 WSL 상태를 확인했다.

wsl --version
wsl --status
wsl -l -v

명령어 설명:

  • wsl --version: 설치된 WSL 버전 확인
  • wsl --status: WSL 기본 설정 및 상태 확인
  • wsl -l -v: 설치된 Linux 배포판과 WSL 버전 확인

3.3 Docker Engine 실행 확인

Docker Desktop 실행 이후 다음 명령어로 Docker 환경이 정상적으로 구성되었는지 확인했다.

docker --version
docker compose version
docker info

docker --version은 설치된 Docker CLI 버전을 확인하고, docker compose version은 Docker Compose 버전을 확인한다.

docker info는 Docker 클라이언트와 서버 엔진 정보를 조회한다.

 

최초에는 Docker Linux 엔진에 연결할 수 없다는 오류가 발생했으나, WSL 2 환경을 구성하고 Docker Desktop을 정상적으로 실행한 후 해결되었다.


4. MySQL 컨테이너 구축

게시판 애플리케이션에서 사용할 데이터베이스를 Docker 컨테이너로 구성했다.

4.1 Docker Compose 파일 생성

프로젝트 최상위 디렉터리에서 다음 명령어로 compose.yaml 파일을 생성했다.

notepad compose.yaml

최초에는 MySQL 서비스만 정의하여 데이터베이스가 정상적으로 실행되는지 확인했다.

services:
  mysql:
    image: mysql:8.0
    container_name: selfsupport-mysql
    environment:
      MYSQL_ROOT_PASSWORD: local_root_password
      MYSQL_DATABASE: board
      MYSQL_USER: example_user
      MYSQL_PASSWORD: local_dev_password
    ports:
      - "127.0.0.1:3307:3306"
    volumes:
      - board_mysql_data:/var/lib/mysql
    restart: unless-stopped

volumes:
  board_mysql_data:

 

주요 설정은 다음과 같다.

설정
설명
image 실행할 MySQL 이미지 및 버전
container_name Docker 컨테이너 이름
MYSQL_DATABASE 생성할 데이터베이스 이름
MYSQL_USER 애플리케이션에서 사용할 DB 계정
MYSQL_PASSWORD DB 사용자 비밀번호
ports 호스트와 컨테이너 포트 연결
volumes DB 데이터 영속성 유지
restart 컨테이너 재시작 정책

여기서 127.0.0.1:3307:3306은 Windows 호스트의 3307 포트를 컨테이너 내부 MySQL 3306 포트에 연결한다는 의미다.

또한 데이터베이스 데이터를 Docker Volume에 저장하여 컨테이너 재생성 시에도 데이터가 유지되도록 구성했다.

※ 위 비밀번호는 로컬 실습용 예시이며, 실제 운영 환경에서는 비밀정보를 별도로 관리해야 한다.

4.2 MySQL 컨테이너 실행

다음 명령어로 컨테이너를 생성하고 실행했다.

docker compose up -d
  • docker compose: Compose 설정을 이용한 컨테이너 관리
  • up: 서비스 생성 및 실행
  • -d: 백그라운드 실행

실행 상태는 다음 명령어로 확인했다.

docker compose ps

 

4.3 MySQL 접속 확인

실행 중인 MySQL 컨테이너 내부에서 MySQL 클라이언트를 실행했다.

docker exec -it selfsupport-mysql mysql -u example_user -p board

주요 옵션의 의미는 다음과 같다.

  • docker exec: 실행 중인 컨테이너에서 명령어 실행
  • -it: 대화형 터미널 사용
  • selfsupport-mysql: 대상 컨테이너
  • mysql: MySQL 클라이언트
  • -u: DB 사용자 계정 지정
  • -p: 비밀번호 입력
  • board: 사용할 데이터베이스

접속 후 다음 SQL을 실행했다.

SHOW DATABASES;
SHOW TABLES;

이를 통해 데이터베이스 연결 상태를 확인했다.


5. Spring Boot Docker 이미지 구성

MySQL 실행을 확인한 후 Spring Boot 게시판 애플리케이션을 컨테이너에서 실행하도록 구성했다.

기존 프로젝트는 Gradle 기반이며, 게시판과 결제 시스템의 실행 클래스가 별도로 존재한다.

따라서 게시판 실행 클래스를 명시적으로 지정할 필요가 있었다.

5.1 Dockerfile 생성

프로젝트 최상위 디렉터리에 확장자 없는 Dockerfile을 생성했다.

notepad Dockerfile

작성한 내용은 다음과 같다.

# Java 17 환경에서 빌드
FROM eclipse-temurin:17-jdk AS build

WORKDIR /app

COPY . .

RUN chmod +x gradlew

# 게시판 실행 클래스 지정
RUN printf "\nspringBoot { mainClass = 'com.projectboard.boardproject.BoardProjectApplication' }\n" >> build.gradle

RUN ./gradlew bootJar -x test --no-daemon

# Spring Boot 실행 환경
FROM eclipse-temurin:17-jre

WORKDIR /app

COPY --from=build /app/build/libs/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=board"]

이 Dockerfile은 빌드 환경과 실행 환경을 구분하는 Multi-stage Build 방식으로 작성했다.

 

빌드 단계

Java 17 JDK 이미지를 이용해 Gradle로 Spring Boot 애플리케이션을 빌드한다.

 

실행 단계

빌드 결과물인 JAR 파일을 Java 17 JRE 환경으로 복사하고 실행한다.

이를 통해 호스트 Windows에 Java를 직접 설치하지 않아도 Spring Boot 애플리케이션을 실행할 수 있다.

참고로 실행 클래스 지정은 기존 build.gradle을 직접 수정하지 않고 Docker 이미지 빌드 과정에서 설정을 추가한 방식이다.

5.2 Dockerfile 오류 해결

최초 빌드 과정에서 다음 오류가 발생했다.

failed to solve: no build stage in current context

Dockerfile 내용이 일부만 저장되면서 첫 번째 FROM 명령어가 누락된 것이 원인이었다.

Dockerfile은 FROM 명령어로 사용할 기본 이미지를 지정해야 한다.

 

내용을 수정한 후 다음 명령어로 확인했다.

Get-Content Dockerfile

 

Dockerfile의 첫 번째 빌드 단계가 정상적으로 정의되었는지 확인한 뒤 다시 빌드를 진행했다.


6. Spring Boot와 MySQL 컨테이너 연결

MySQL과 Spring Boot를 함께 실행하기 위해 기존 compose.yaml에 애플리케이션 서비스를 추가했다.

최종 구성은 다음과 같다.

services:
  mysql:
    image: mysql:8.0
    container_name: selfsupport-mysql
    environment:
      MYSQL_ROOT_PASSWORD: local_root_password
      MYSQL_DATABASE: board
      MYSQL_USER: example_user
      MYSQL_PASSWORD: local_dev_password
    ports:
      - "127.0.0.1:3307:3306"
    volumes:
      - board_mysql_data:/var/lib/mysql
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD"]
      interval: 10s
      timeout: 5s
      retries: 10

  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: selfsupport-board
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: board
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/board?useSSL=false&allowPublicKeyRetrieval=true
      SPRING_DATASOURCE_USERNAME: example_user
      SPRING_DATASOURCE_PASSWORD: local_dev_password
    depends_on:
      mysql:
        condition: service_healthy

volumes:
  board_mysql_data:

 

여기서 중요한 부분은 Spring Boot에서 사용하는 DB 연결 주소다.

SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/board?useSSL=false&allowPublicKeyRetrieval=true

 

Docker 컨테이너 내부에서 localhost는 해당 컨테이너 자신을 가리킨다.

따라서 Spring Boot 컨테이너에서 MySQL 컨테이너에 연결하려면 Docker Compose 서비스 이름인 mysql을 호스트명으로 사용해야 한다.

또한 depends_on과 Healthcheck를 이용하여 MySQL이 정상적인 연결 가능 상태에 도달한 다음 애플리케이션이 시작되도록 구성했다.

6.1 Compose 설정 검증

docker compose config --quiet

위 명령어로 Compose 설정 파일의 구문과 구성을 검증했다.

6.2 애플리케이션 빌드 및 실행

docker compose up --build -d

주요 옵션은 다음과 같다.

  • up: 서비스 실행
  • --build: 애플리케이션 이미지 빌드
  • -d: 백그라운드 실행

 

빌드가 완료되면 다음 명령어로 실행 상태를 확인할 수 있다.

docker compose ps

정상 실행되면 MySQL과 Spring Boot 컨테이너가 모두 Up 상태로 표시된다.


7. 게시판 초기 데이터 오류 해결

Docker 환경 구축 이후 브라우저에서 게시판에 정상적으로 접속할 수 있었다.

하지만 게시글이 표시되지 않았으며, 기존 테스트 계정으로도 로그인할 수 없는 문제가 발생했다.

7.1 사용자 데이터 확인

MySQL 컨테이너에서 다음 명령어를 실행했다.

docker exec selfsupport-mysql mysql -u example_user -plocal_dev_password board -e "SELECT COUNT(*) AS users FROM user_account; SELECT COUNT(*) AS articles FROM article;"

여기서 -e 옵션은 MySQL에 접속하여 지정한 SQL을 실행한다는 의미다.

확인 결과 사용자 계정은 0건이었으며, 초기 데이터가 적재되지 않은 상태였다.

7.2 Flyway 설정 확인

기존 게시판 설정 파일에는 다음과 같은 구성이 있었다.

spring:
  flyway:
    enabled: true
    locations: classpath:db/migration/board

[selfsupport-site-main\src\main\resources\application-board.yaml]

 

하지만 build.gradle에서 Flyway 의존성이 확인되지 않았다.

또한 데이터베이스에서 Flyway 실행 이력 테이블을 조회한 결과 다음 오류가 발생했다.

ERROR 1146 (42S02):
Table 'board.flyway_schema_history' doesn't exist

 

즉, Flyway 마이그레이션이 정상적으로 수행되지 않은 것으로 판단했다.

7.3 Hibernate 테이블 초기화 설정 변경

기존 설정은 다음과 같았다.

hibernate.ddl-auto: create

[selfsupport-site-main\src\main\resources\application-board.yaml]

 

create 옵션은 애플리케이션이 실행될 때 Hibernate가 테이블을 새로 생성하는 설정이다.

보안 진단 환경에서는 테스트 계정과 게시글을 유지해야 하기 때문에 다음과 같이 변경했다.

hibernate.ddl-auto: validate

validate 옵션은 기존 테이블 구조와 JPA 엔티티의 매핑이 일치하는지 확인하며, 테이블을 자동으로 삭제하거나 재생성하지 않는다.

이후 애플리케이션 이미지를 다시 빌드해 변경 사항을 반영했다.

7.4 초기 데이터 SQL 확인

기존 소스코드에는 다음 SQL 파일이 존재했다.

src/main/resources/db/migration/board/
├── V1__create_board_tables.sql
└── V2__seed_board.sql

V1__create_board_tables.sql은 테이블 생성용 SQL이고, V2__seed_board.sql은 테스트 계정 및 게시글 데이터를 생성하는 SQL이다.

 

이미 Hibernate를 통해 테이블이 생성된 상태였으므로 V1은 실행하지 않았다.

먼저 현재 테이블 구조를 확인했다.

docker exec selfsupport-mysql mysql -u example_user -plocal_dev_password board -e "DESCRIBE user_account; DESCRIBE article;"

초기 데이터 SQL에서 사용하는 컬럼과 실제 테이블 구조를 비교한 후 V2 SQL을 적용했다.

7.5 테스트 데이터 적재

프로젝트 최상위 디렉터리에서 다음 명령어를 실행했다.

Get-Content .\src\main\resources\db\migration\board\V2__seed_board.sql -Raw -Encoding UTF8 | docker exec -i selfsupport-mysql mysql -u example_user -plocal_dev_password --default-character-set=utf8mb4 board

이 명령어는 SQL 파일의 내용을 읽어 Docker 컨테이너 내부의 MySQL로 전달한다.

  • Get-Content: 파일 내용 읽기
  • -Raw: 전체 내용을 하나의 문자열로 읽기
  • -Encoding UTF8: UTF-8 인코딩 사용
  • |: 앞 명령어의 출력을 다음 명령어로 전달
  • docker exec -i: 컨테이너 내부 명령어에 표준 입력 전달
  • --default-character-set=utf8mb4: MySQL 문자 인코딩 지정

SQL 적용 이후 사용자와 게시글 데이터를 다시 조회했다.

SELECT COUNT(*) AS users FROM user_account;
SELECT COUNT(*) AS articles FROM article;

테스트 사용자 2명과 게시글 123개가 적재된 것을 확인했다.

※ 기존 데이터가 존재하는 DB에 초기화 SQL을 반복 실행하면 중복 키 오류나 데이터 충돌이 발생할 수 있으므로, 최초 데이터 적재 시에만 적용해야 한다.


8. 게시판 정상 실행 확인

환경 구성과 초기 데이터 적재를 완료한 후 브라우저를 통해 게시판에 접속했다.

http://localhost:8080

테스트 계정으로 로그인하고 게시글 목록이 정상적으로 표시되는 것을 확인했다.

 

또한 Docker 컨테이너에서 MySQL과 Spring Boot가 정상적으로 실행되고 있음을 확인했다.

이로써 기존 Spring Boot 게시판을 Docker 기반 로컬 환경에서 실행하기 위한 구성을 완료했다.


9. 향후 웹 보안 취약점 분석 계획

다음 단계에서는 구축한 환경을 대상으로 실제 웹 보안 취약점 분석을 진행할 예정이다.

먼저 Burp Suite를 통해 게시판의 HTTP 요청과 응답을 분석하고 주요 기능의 입력값과 접근통제 로직을 확인한다.

주요 분석 항목

구분
분석 내용
인증 및 세션 로그인 처리, 인증 상태, 세션 관리
접근통제 타인 게시글·댓글 수정 및 삭제 가능 여부
XSS 게시글·댓글 입력값의 출력 처리
CSRF 상태 변경 요청에 대한 위조 방어
SQL Injection 사용자 입력값의 DB 쿼리 처리
보안 설정 Spring Security 및 HTTP 보안 설정

 

취약점 분석은 다음과 같은 과정으로 진행할 계획이다.

기능 분석
    ↓
HTTP 요청·응답 분석
    ↓
취약점 후보 식별
    ↓
소스코드 정적 분석
    ↓
PoC 검증
    ↓
취약점 원인 분석
    ↓
시큐어코딩 및 개선
    ↓
재검증

취약점이 확인되면 발생 원인과 재현 과정을 상세히 정리하고, 수정 전후의 코드와 동작을 비교할 예정이다.

마무리

이번 작업에서는 직접 개발했던 Spring Boot 웹 애플리케이션을 대상으로 Docker 기반의 보안 진단 환경을 구축했다.

Docker Desktop과 WSL 2를 설치하고, MySQL 및 Spring Boot를 별도 컨테이너로 구성했다.

 

또한 DB 초기 데이터가 정상적으로 적재되지 않는 문제를 확인하고, Hibernate 설정 변경과 기존 SQL 적용을 통해 게시판이 정상 동작하도록 복구했다.

 

이번 환경 구축을 통해 웹 애플리케이션과 데이터베이스를 컨테이너 단위로 관리하고, 동일한 조건에서 보안 진단을 반복 수행할 수 있는 기반을 마련했다.

다음 글에서는 Burp Suite를 연결해 게시판 기능을 분석하고, 인증·인가 및 접근통제 취약점부터 진단할 예정이다.

1. Lab 개요

이번 실습은 상품 카테고리 필터에서 발생하는 SQL Injection 취약점을 이용하여, 현재 Query가 조회하고 있는 상품 테이블이 아닌 다른 테이블의 데이터를 조회하는 문제이다.

 

앞선 실습에서는 UNION SELECT를 이용하여 다음 두 가지를 확인하였다.

  • 기존 Query가 반환하는 컬럼의 개수
  • 문자열 데이터를 출력할 수 있는 컬럼

 

이번 Lab에서는 이를 실제 데이터 조회에 활용한다.

 

The database contains a different table called users, with columns called username and password.
To solve the lab, perform a SQL injection UNION attack that retrieves all usernames and passwords, and use the information to log in as the administrator user.

데이터베이스에는 users 라는 이름의 다른 테이블이 있으며, 그 테이블에는 username 와 password 라는 이름의 열이 있습니다.
이 실습 과제를 해결하려면 SQL 인젝션 UNION 공격을 수행하여 모든 사용자 이름과 비밀번호를 추출하고, 해당 정보를 사용하여 해당 administrator 사용자(관리자)로 로그인하십시오.

 

Lab 설명을 통해 데이터베이스에 다음과 같은 테이블이 존재한다는 정보가 제공된다.

Table : users
 

 

또한 users 테이블에는 다음 두 개의 컬럼이 존재한다.

username
password
 

 

따라서 이번 실습의 목표는 UNION SELECT를 이용하여 users 테이블의 username, password 값을 조회하고, 그중 administrator 계정의 비밀번호를 확인하여 관리자 계정으로 로그인하는 것이다.


 

2. 실습 환경 및 취약한 파라미터 확인

Lab에 접속하면 상품 목록과 함께 여러 카테고리 필터를 확인할 수 있다.

 

특정 카테고리를 선택하면 다음과 같은 요청이 발생한다.

/filter?category=Accessories
 

 

여기서 SQL Injection 공격 지점은 다음 파라미터이다.

category
 

 

서버에서는 해당 값을 SQL Query에 사용하여 선택한 카테고리의 상품을 조회하는 것으로 볼 수 있다.

 

따라서 Burp Suite에서 해당 요청을 확인한 뒤 Repeater로 전송하여 category 파라미터를 변조한다.


 

3. SQL Injection 취약점 확인

먼저 category 파라미터에 작은따옴표(')를 추가하여 서버의 반응을 확인하였다.

Accessories'
 

해당 요청을 전송하자 서버에서 500 Internal Server Error가 발생하였다.

 

이는 입력값에 의해 SQL Query의 문법 구조가 깨졌을 가능성을 나타낸다.

 

이후 다음과 같이 정상적으로 참이 되는 조건과 주석을 추가하였다.

Accessories' OR 1=1--
 

 

해당 요청에서는 다시 정상적인 200 OK 응답이 반환되었다.

 

이를 통해 category 입력값이 SQL Query 구조에 영향을 줄 수 있으며, 해당 파라미터에서 SQL Injection이 가능함을 확인할 수 있다.


 

4. 기존 Query의 컬럼 수 확인

UNION SELECT를 이용해 다른 테이블의 데이터를 조회하려면 먼저 기존 Query와 동일한 개수의 컬럼을 반환해야 한다.

따라서 NULL 값을 이용하여 컬럼 수를 확인하였다.

 

먼저 하나의 컬럼으로 요청하였다.

' UNION SELECT NULL--

 

 

해당 요청에서는 오류가 발생하였다.

 

다음으로 두 개의 컬럼을 지정하였다.

' UNION SELECT NULL,NULL--
 

 

이번에는 정상적인 200 OK 응답이 반환되었다.

 

따라서 기존 SQL Query가 반환하는 컬럼의 개수는 다음과 같다.

2개
 

즉 이후 UNION SELECT 공격에서도 두 개의 값을 반환하도록 구성해야 한다.


 

5. 문자열 데이터 호환 여부 확인

이번 Lab에서 조회하려는 username과 password는 모두 문자열 데이터이다.

따라서 두 컬럼 모두 문자열 데이터를 반환할 수 있는지도 확인할 필요가 있다.

 

다음과 같이 두 컬럼에 임의의 문자열을 삽입하였다.

' UNION SELECT 'abc','def'--
 

 

요청을 전송한 결과 정상적인 응답이 반환되었다.

따라서 두 컬럼 모두 문자열 데이터와 호환되는 컬럼임을 확인할 수 있다.

 

이를 정리하면 다음과 같다.

1번째 컬럼 → 문자열 사용 가능
2번째 컬럼 → 문자열 사용 가능
 

따라서 users 테이블의 username과 password를 각각 두 컬럼에 대응시켜 조회할 수 있다.


 

6. users 테이블 데이터 조회

The database contains a different table called users, with columns called username and password.
To solve the lab, perform a SQL injection UNION attack that retrieves all usernames and passwords, and use the information to log in as the administrator user.

데이터베이스에는 users 라는 이름의 다른 테이블이 있으며, 그 테이블에는 username 와 password 라는 이름의 열이 있습니다.
이 실습 과제를 해결하려면 SQL 인젝션 UNION 공격을 수행하여 모든 사용자 이름과 비밀번호를 추출하고, 해당 정보를 사용하여 해당 administrator 사용자(관리자)로 로그인하십시오.

 

이번 Lab에서는 데이터베이스에 users 테이블이 존재하고, 해당 테이블에 다음 컬럼이 존재한다는 정보가 제공된다.

username
password
 

 

따라서 다음과 같은 Payload를 구성하였다.

' UNION SELECT username,password FROM users--

 

구문을 나누어 보면 다음과 같다.

UNION SELECT
 

 

기존 SQL Query의 결과에 새로운 Query 결과를 추가한다.

username,password
 

 

조회할 두 개의 컬럼을 지정한다.

FROM users
 

데이터를 가져올 users 테이블을 지정한다.

 

마지막으로,

--
 

이후에 이어지는 기존 SQL Query를 주석 처리한다.

 

따라서 전체 Payload는 다음과 같다.

' UNION SELECT username,password FROM users--

 

7. 사용자 계정 정보 확인

 

 

Payload를 전송하면 기존 상품 데이터와 함께 users 테이블의 데이터가 추가로 반환된다.

 

즉 원래 화면에는 상품명과 상품 설명 등이 출력되지만, UNION SELECT를 통해 추가된 행에는 다음과 같이 사용자 정보가 표시된다.

administrator    [password]
carlos           [password]
wiener           [password]
 

 

실제 계정명과 비밀번호 값은 Lab이 생성될 때마다 달라질 수 있다.

이번 실습에서는 이 중 다음 계정을 확인한다. (관리자 계정 정보이다.)

administrator
 

 

그리고 해당 행에 함께 출력된 비밀번호 값을 확인한다.

 

administrators
l5bfjn3kthu92sderd3u

 

이로써 SQL Injection을 통해 원래 상품 Query와 직접적인 관계가 없는 users 테이블의 계정 정보까지 조회할 수 있음을 확인하였다.


 

8. administrator 계정 로그인

확인한 관리자 계정 정보를 이용하여 My account 페이지에서 로그인을 시도한다.

 

Username에는 다음 값을 입력한다.

administrator
 

 

Password에는 앞선 SQL Injection을 통해 확인한 비밀번호를 입력한다.

Username : administrator
Password : l5bfjn3kthu92sderd3u
 

 

 

Log in 버튼을 선택하면 관리자 계정으로 로그인이 성공한다.


 

9. 결과 확인

로그인 이후 현재 사용자가 administrator로 표시되는 것을 확인할 수 있다.

또한 Lab 상태가 Solved로 변경되면서 실습이 정상적으로 완료되었다.

 

이번 실습에서는 단순히 SQL Query의 조건을 조작하는 수준을 넘어,

' UNION SELECT username,password FROM users--
 

구문을 이용하여 다른 테이블에 저장된 사용자 계정 정보까지 조회할 수 있었다.


10. 핵심 정리

이번 실습의 핵심은 UNION 기반 SQL Injection을 이용하여 현재 Query가 조회하는 테이블이 아닌 다른 테이블의 데이터를 가져오는 것이다.

 

먼저 기존 Query의 컬럼 수를 확인하였다.

' UNION SELECT NULL--
500 Internal Server Error
 

 

다음으로 두 개의 컬럼을 사용하였다.

' UNION SELECT NULL,NULL--
200 OK
 

 

따라서 기존 Query가 반환하는 컬럼은 2개임을 확인하였다.

 

또한 다음 Payload를 이용하여 두 컬럼 모두 문자열을 처리할 수 있음을 확인하였다.

' UNION SELECT 'abc','def'--
 

 

이후 Lab에서 제공한 테이블 및 컬럼 정보를 이용하여 다음 Payload를 구성하였다.

' UNION SELECT username,password FROM users--
 

 

그 결과 users 테이블에 저장된 사용자 이름과 비밀번호가 응답에 포함되었으며, 그중 administrator 계정의 비밀번호를 확인할 수 있었다.

 

최종적으로 해당 계정 정보를 이용하여 로그인함으로써 Lab을 해결하였다.

 

전체 흐름을 정리하면 다음과 같다.

category 파라미터 확인
        ↓
SQL Injection 취약점 확인
        ↓
UNION SELECT 컬럼 수 확인
        ↓
2개 컬럼 확인
        ↓
두 컬럼의 문자열 호환 여부 확인
        ↓
users 테이블의 username/password 조회
        ↓
administrator 비밀번호 확인
        ↓
관리자 계정 로그인
        ↓
Solved

 

 

 

1. Lab 개요

이번 실습은 상품 카테고리 필터에서 발생하는 SQL Injection 취약점을 이용하여, 기존 SQL Query가 반환하는 컬럼 중 문자열(String) 데이터를 저장할 수 있는 컬럼을 확인하는 문제이다.

 

이전 실습에서는 UNION SELECT 구문에 NULL 값을 하나씩 추가하여 기존 Query가 총 3개의 컬럼을 반환한다는 것을 확인하였다.

' UNION SELECT NULL,NULL,NULL--
 

 

하지만 실제로 다른 테이블의 문자열 데이터를 조회하려면 단순히 컬럼 개수만 알아서는 부족하다.

UNION으로 결합되는 각 컬럼은 기존 Query의 컬럼과 호환되는 데이터 타입을 가져야 하기 때문이다.

 

따라서 이번 실습의 목표는 3개의 컬럼 중 하나씩 문자열 값을 삽입하여, 어떤 컬럼이 문자열 데이터를 처리할 수 있는지 확인하는 것이다.

Lab에서는 임의의 문자열 값을 제공하며, 최종적으로 해당 문자열이 Query 결과에 출력되도록 하면 실습이 완료된다.


 

2. 실습 환경 및 취약한 파라미터 확인

Lab에 접속하면 여러 상품과 함께 카테고리 필터를 확인할 수 있다.

 

특정 카테고리를 선택하면 다음과 같은 형태의 요청이 발생한다.

/filter?category=Gifts
 

여기서 SQL Injection 공격 지점은 다음 파라미터이다.

category
 

 

이전 실습과 동일하게 서버에서는 해당 값(category)을 SQL Query의 조건으로 사용하는 것으로 볼 수 있다.

SELECT ...
FROM products
WHERE category = 'Gifts'
 

 

따라서 이번 실습에서도 category 파라미터를 변조하여 UNION 기반 SQL Injection을 수행한다.


 

3. 기존 Query의 컬럼 수 확인

먼저 이전 실습에서 사용했던 Payload를 이용하여 기존 Query의 컬럼 수를 다시 확인하였다.

' UNION SELECT NULL,NULL,NULL--
 

 

해당 요청을 전송했을 때 정상적인 응답이 반환되므로,

기존 Query가 반환하는 컬럼 수는 다음과 같다.

3개
 

 

따라서 이후 테스트에서도 반드시 UNION SELECT 뒤에 총 3개의 값을 지정해야 한다.

예를 들어 다음과 같은 형태가 된다.

UNION SELECT value1,value2,value3
 

 

이제 이 3개의 위치 중 어느 위치가 문자열 데이터를 받을 수 있는지 확인해야 한다.


 

4. 첫 번째 컬럼 문자열 호환 여부 확인

먼저 첫 번째 컬럼에 임의의 문자열을 넣고 나머지 컬럼에는 NULL을 사용하였다.

 
' UNION SELECT 'test',NULL,NULL--
 

 

이를 각 컬럼별로 보면 다음과 같다.

1번째 컬럼 : 'test'
2번째 컬럼 : NULL
3번째 컬럼 : NULL
 

 

요청을 전송한 결과 서버에서 500 Internal Server Error가 발생하였다.

이는 첫 번째 컬럼의 데이터 타입이 문자열과 호환되지 않는다는 것을 의미한다.

따라서 다음 컬럼으로 문자열 값을 이동하여 확인한다.


 

5. 두 번째 컬럼 문자열 호환 여부 확인

이번에는 첫 번째 값을 다시 NULL로 변경하고, 두 번째 컬럼에 문자열을 삽입하였다.

' UNION SELECT NULL,'test',NULL--
 

 

각 컬럼의 값은 다음과 같다.

1번째 컬럼 : NULL
2번째 컬럼 : 'test'
3번째 컬럼 : NULL
 

 

요청을 전송한 결과 이번에는 200 OK 응답이 반환되었다.

 

즉, 두 번째 컬럼은 문자열 타입과 호환되는 것을 확인할 수 있다.

결과적으로 다음과 같이 정리할 수 있다.

1번째 컬럼 → 문자열 사용 불가
2번째 컬럼 → 문자열 사용 가능
 

따라서 이후 문자열 데이터를 출력하려면 두 번째 컬럼을 사용하면 된다.


 

6. Lab에서 제공된 문자열 출력

이번 Lab에서는 특정 랜덤 문자열을 Query 결과에 출력하도록 요구한다.

make the database retrieve the string: 'jYFcnD'
“데이터베이스가 jYFcnD라는 문자열을 조회 결과에 포함해서 반환하도록 만들어라.”

 

예를 들어 Lab에서 다음과 같은 문자열이 제공되었다고 가정하면,

jYFcnD
 

 

앞서 문자열 사용이 가능하다고 확인한 두 번째 컬럼에 해당 값을 삽입한다.

 

 

Payload 구조를 보면 다음과 같다.

1번째 컬럼 : NULL
2번째 컬럼 : 'jYFcnD'
3번째 컬럼 : NULL
 

서버에서 Query가 정상적으로 실행되면 두 번째 컬럼을 통해 해당 문자열이 애플리케이션 응답에 포함된다.

※ 실제 문자열은 Lab을 실행할 때마다 제공되는 값을 사용하면 된다.


 

7. 결과 확인

 

Payload를 전송한 결과 Lab에서 제공한 문자열이 응답 화면에 표시되는 것을 확인하였다.

jYFcnD
 

이는 UNION SELECT의 두 번째 컬럼이 애플리케이션 화면에 출력되며, 동시에 문자열 데이터를 처리할 수 있는 컬럼이라는 것을 의미한다.

 

또한 Lab 상태가 Solved로 변경되면서 실습이 정상적으로 완료되었다.


8. 핵심 정리

이번 실습의 핵심은 UNION 기반 SQL Injection에서 문자열 데이터를 출력할 수 있는 컬럼을 찾는 것이다.

 

이전 실습을 통해 기존 Query가 3개의 컬럼을 반환한다는 것을 확인하였다.

' UNION SELECT NULL,NULL,NULL--
 

이후 각 컬럼에 문자열 값을 하나씩 삽입하면서 서버 응답을 확인하였다.

 

첫 번째 컬럼에 문자열을 삽입하면,

' UNION SELECT 'test',NULL,NULL--
500 Internal Server Error
 

 

오류가 발생하였다.

반면 두 번째 컬럼에 문자열을 삽입하면,

' UNION SELECT NULL,'test',NULL--
200 OK
 

정상적인 응답이 반환되었다.

 

따라서 두 번째 컬럼이 문자열 데이터와 호환되는 컬럼임을 확인할 수 있었다.

최종적으로 Lab에서 제공된 문자열을 두 번째 컬럼에 삽입하였다.

' UNION SELECT NULL,'jYFcnD',NULL--
 

 

그 결과 해당 문자열이 애플리케이션 응답에 출력되면서 Lab을 해결할 수 있었다.

이번 실습의 흐름을 간단히 정리하면 다음과 같다.

컬럼 수 확인
        ↓
3개 컬럼 확인
        ↓
'text', NULL, NULL
        ↓
500 Error
        ↓
NULL, 'text', NULL
        ↓
200 OK
        ↓
2번째 컬럼이 문자열 호환
        ↓
Lab에서 제공된 문자열 삽입
        ↓
Solved

 

 

 

1. Lab 개요

 

이번 실습은 상품 카테고리 필터에서 발생하는 SQL Injection 취약점을 이용하여, 원본 SQL Query가 반환하는 컬럼(Column)의 개수를 확인하는 문제이다.

 

SQL Injection에서 UNION SELECT 구문을 이용하면 기존 Query의 결과에 공격자가 작성한 Query의 결과를 추가할 수 있다.

 

하지만 UNION을 사용하기 위해서는 두 Query가 반환하는 컬럼의 개수가 동일해야 한다.

 

예를 들어 기존 Query가 3개의 컬럼을 반환한다면 다음과 같이 동일하게 3개의 값을 지정해야 한다.

UNION SELECT NULL, NULL, NULL
 

따라서 이번 실습의 목표는 UNION SELECT에 NULL 값을 하나씩 추가하면서 서버의 응답을 확인하고, 기존 Query가 몇 개의 컬럼을 반환하는지 파악하는 것이다.


2. 실습 환경 확인

Lab에 접속하면 여러 상품과 함께 상품을 분류할 수 있는 카테고리 필터를 확인할 수 있다.

 

 

특정 카테고리를 선택하면 해당 카테고리에 속하는 상품만 조회되는 구조이다.

 

 

예를 들어 Tech gifts 카테고리를 선택하면 URL에서 다음과 같은 요청을 확인할 수 있다.

/filter?category=Tech gifts
 

 

여기서 핵심이 되는 파라미터는 다음과 같다.

category
 

 

서버에서는 해당 값을 이용하여 개념적으로 다음과 같은 SQL Query를 실행하는 것으로 볼 수 있다.

SELECT ...
FROM products
WHERE category = 'Tech gifts'
 

 

따라서 이번 실습에서는 category 파라미터를 SQL Injection 공격 지점으로 사용한다.


 

3. SQL Injection 취약점 확인

본격적인 UNION 공격을 수행하기 전에, 먼저 해당 파라미터에서 SQL Injection이 발생하는지 확인하였다.

 

category 파라미터에 작은따옴표(')를 추가하여 요청을 전송하였다.

Tech gifts'
 

 

그 결과 정상적인 상품 페이지 대신 서버에서 500 Internal Server Error가 발생하였다.

이는 입력한 작은따옴표로 인해 서버 내부 SQL Query의 문법 구조가 깨졌을 가능성을 나타낸다.

 

추가적으로 정상적인 SQL 조건을 구성할 수 있는지 확인하기 위해 다음과 같은 형태의 입력값을 사용할 수 있다.

Tech gifts' AND 1=1--
 

1=1은 항상 참(TRUE)이 되는 조건이며, --는 이후 SQL 구문을 주석 처리한다.

정상적인 응답이 반환된다면 사용자 입력값이 SQL Query 구조에 영향을 주고 있음을 확인할 수 있다.

 

이번 Lab에서는 이를 통해 category 파라미터에서 SQL Injection이 가능한 것을 확인한 뒤, UNION SELECT 공격을 진행하였다.


4. UNION SELECT를 이용한 컬럼 수 확인

이번 실습의 핵심은 UNION SELECT를 이용하여 기존 Query가 반환하는 컬럼의 개수를 확인하는 것이다.

UNION을 사용하기 위해서는 기존 Query와 새로 추가하는 Query의 컬럼 수가 동일해야 한다.

 

먼저 하나의 NULL 값을 이용하여 다음과 같이 요청하였다.

' UNION SELECT NULL--

 

 

 

하지만 서버에서는 500 Internal Server Error가 발생하였다.

 

이는 기존 Query가 반환하는 컬럼의 개수가 1개가 아니라는 것을 의미한다.


 

 

다음으로 NULL 값을 하나 추가하여 두 개의 컬럼을 지정하였다.

' UNION SELECT NULL, NULL--
 

 

 

해당 요청 역시 서버에서 500 Internal Server Error가 발생하였다.

 

따라서 기존 Query가 반환하는 컬럼의 개수는 2개도 아닌 것을 확인할 수 있다.

 


 

 

마지막으로 NULL 값을 하나 더 추가하여 다음과 같이 요청하였다.

' UNION SELECT NULL, NULL, NULL--
 

 

이번에는 이전과 달리 오류가 발생하지 않고 정상적인 응답이 반환되었다.

 

즉,

NULL 1개 → 오류
NULL 2개 → 오류
NULL 3개 → 정상 응답
 

이라는 결과를 확인할 수 있었다.

 

따라서 기존 SQL Query가 반환하는 컬럼의 개수는 3개임을 알 수 있다.


 

5. NULL 값을 사용하는 이유

UNION SELECT 공격에서는 다음과 같이 숫자나 문자열을 사용할 수도 있다.

UNION SELECT 1, 2, 3
 

 

하지만 UNION을 사용하기 위해서는 단순히 컬럼의 개수뿐 아니라 각 컬럼의 데이터 타입도 서로 호환되어야 한다.

 

예를 들어 기존 Query의 첫 번째 컬럼이 문자열인데 공격자가 숫자 값을 넣는 경우, 데이터 타입 문제로 인해 Query가 실패할 수 있다.

 

이 때문에 컬럼 수를 확인하는 단계에서는 일반적으로 다음과 같이 NULL 값을 사용한다.

UNION SELECT NULL, NULL, NULL
 

NULL은 대부분의 데이터 타입과 호환될 수 있기 때문에, 실제 컬럼의 데이터 타입을 알지 못하는 상황에서도 비교적 안전하게 사용할 수 있다.

 

따라서 UNION 기반 SQL Injection에서는 컬럼 수를 탐색할 때 NULL 값을 하나씩 추가하는 방식이 자주 사용된다.


 

6. 결과 확인

다음 Payload를 전송한 결과,

' UNION SELECT NULL, NULL, NULL--
 

 

서버에서 정상적인 응답이 반환되었으며 Lab이 Solved 상태로 변경되었다.

 

이를 통해 기존 SQL Query가 반환하는 컬럼의 개수는 다음과 같다는 것을 확인하였다.

3개
 

 

즉, 이후 UNION SELECT 공격을 수행하려면 기본적으로 다음과 같이 3개의 컬럼을 구성해야 한다.

UNION SELECT value1, value2, value3
 

 


7. 핵심 정리

이번 실습의 핵심은 UNION 기반 SQL Injection을 수행하기 전에 기존 Query가 반환하는 컬럼의 개수를 파악하는 것이다.

UNION 연산을 사용하려면 기존 Query와 공격자가 삽입한 Query의 컬럼 개수가 동일해야 한다.

 

따라서 다음과 같이 NULL 값을 하나씩 증가시키며 서버의 응답을 확인하였다.

' UNION SELECT NULL--
500 Internal Server Error
 
' UNION SELECT NULL, NULL--
500 Internal Server Error
 
' UNION SELECT NULL, NULL, NULL--
정상 응답
 

 

결과적으로 NULL 값을 3개 사용했을 때 Query가 정상적으로 실행되었으므로, 기존 Query가 반환하는 컬럼은 총 3개임을 확인할 수 있었다.

 

이번 Lab에서 기억할 핵심은 다음과 같다.

  • UNION을 사용하려면 두 Query의 컬럼 개수가 동일해야 한다.
  • 컬럼 수를 확인할 때는 NULL 값을 하나씩 추가하면서 응답을 확인할 수 있다.
  • NULL은 대부분의 데이터 타입과 호환되기 때문에 컬럼 구조를 모르는 상황에서 사용하기 적합하다.
  • 오류가 사라지는 시점의 NULL 개수를 통해 기존 Query의 컬럼 수를 파악할 수 있다.

1. Lab 개요

 

이번 실습은 로그인 기능에서 발생하는 SQL Injection 취약점을 이용하여, 정상적인 비밀번호 인증 과정을 우회하고 administrator 계정으로 로그인하는 문제이다.

 

일반적인 로그인 기능에서는 사용자가 입력한 아이디와 비밀번호를 이용하여 서버에서 다음과 같은 SQL Query를 실행할 수 있다.

SELECT * FROM users
WHERE username = 'administrator'
AND password = 'password'
 

정상적인 경우에는 username과 password가 모두 일치해야 로그인이 성공한다.

 

하지만 이번 Lab에서는 로그인 요청의 username 값이 SQL Query에 안전하게 처리되지 않고 사용되기 때문에 SQL Injection이 발생할 수 있다.

 

따라서 이번 실습의 목표는 username 파라미터에 SQL Injection 구문을 삽입하여 비밀번호 검증 조건을 무력화하고 administrator 계정으로 로그인하는 것이다.

 


2. 실습 환경 확인

Lab에 접속하면 일반적인 쇼핑몰 형태의 메인 화면을 확인할 수 있다.

 

화면 우측 상단에는 다음과 같은 메뉴가 존재한다.

  • Home
  • My account

 

이번 실습은 상품 조회 기능이 아니라 로그인 기능에서 발생하는 SQL Injection 취약점을 다루기 때문에 My account 메뉴를 선택하여 로그인 페이지로 이동하였다.

 

로그인 페이지에서는 다음과 같이 두 개의 입력값을 확인할 수 있다.

  • Username
  • Password

사용자가 해당 값을 입력하고 Log in 버튼을 누르면, 입력한 계정 정보를 서버로 전송하여 인증을 수행하는 구조이다.

 

이번 문제에서는 이 로그인 과정에서 사용되는 username 파라미터가 SQL Query에 안전하게 처리되지 않고 사용되는 점이 핵심이다.

 

따라서 이후 Burp Suite를 이용하여 로그인 요청을 확인한 뒤, username 파라미터를 변조하여 SQL Injection을 수행하고 비밀번호 인증 우회를 시도한다.

 

3. 로그인 요청 및 취약한 파라미터 확인

로그인 페이지에서 임의의 Username과 Password를 입력한 뒤 Burp Suite를 이용해 로그인 요청을 확인하였다.

로그인 요청에는 다음과 같이 사용자가 입력한 계정 정보가 전달된다.

username=test
password=test

 

 

 

이번 실습에서는 이 중 다음 파라미터가 핵심이다.

username
 

 

일반적인 로그인 기능에서는 서버가 입력받은 Username과 Password를 이용하여 다음과 같은 SQL Query를 실행할 수 있다.

SELECT * FROM users
WHERE username = 'administrator'
AND password = 'password'
 

 

즉, 정상적인 경우에는 username과 password가 모두 일치해야 사용자 인증이 성공한다.

 

하지만 이번 Lab에서는 사용자 입력값이 Parameterized Query와 같은 안전한 방식으로 처리되지 않고 SQL Query에 직접 사용되는 취약점이 존재한다.

 

따라서 username 파라미터의 입력값을 조작하면 기존 SQL Query의 구조를 변경할 수 있으며, 이를 이용해 비밀번호 검증 조건을 우회할 수 있다.


4. 관리자 계정명 추정

SQL Injection을 통해 로그인 우회를 시도하기 전에, 우선 어떤 관리자 계정으로 로그인을 시도할지 확인할 필요가 있다.

 

임의의 Username과 Password를 입력하여 로그인을 시도하면 다음과 같은 메시지가 출력된다.

Invalid username or password.
 

이 메시지는 Username이 존재하지 않는 경우와 Password가 틀린 경우를 구분하지 않고 동일한 오류 메시지를 반환한다.

 

따라서 로그인 화면의 응답만을 이용해서 특정 Username의 존재 여부를 직접 확인하기는 어렵다.

 

예를 들어,

admin
administrator
root
 

와 같은 값을 하나씩 입력하더라도 결과가 모두 동일하게 나타날 수 있기 때문에, 이를 통해 실제 존재하는 관리자 계정을 식별할 수는 없다.

 

이번 Lab에서는 관리자 계정으로 일반적으로 많이 사용되는 계정명 중 하나인 다음 값을 사용하여 관리자 계정을 유추하였다.

administrator
 

 

PortSwigger Lab의 목표에서도 관리자 계정이 administrator로 제시되어 있으며, 이후 SQL Injection을 통해 해당 계정의 비밀번호 검증을 우회하는 방식으로 실습을 진행한다.


5. SQL Injection 수행

앞서 확인한 관리자 계정명 administrator를 대상으로 비밀번호 검증을 우회하기 위해, username 파라미터에 다음과 같은 값을 입력하였다.

administrator'--
 

Password 입력란에는 임의의 값을 입력하였다.

예를 들어 다음과 같이 입력할 수 있다.

Username : administrator'--
Password : test
 

Password 값을 입력하는 이유는 로그인 페이지에서 빈 입력값을 허용하지 않는 경우가 있기 때문이다.

하지만 이번 공격에서는 Password 조건 자체가 SQL 주석 처리되기 때문에 실제로 입력한 Password 값은 인증 과정에 영향을 주지 않는다.

 

입력한 Payload를 각각 살펴보면 다음과 같다.

administrator
 

로그인을 시도할 사용자 계정명이다.

이번 Lab에서는 목표 계정이 administrator로 주어져 있으므로 해당 값을 그대로 사용한다.

다음의 작은따옴표는 기존 SQL Query에서 사용되고 있던 문자열을 종료한다.

'
 

 

이후 다음 구문을 사용한다.

--
 

--는 SQL에서 이후 내용을 주석으로 처리하는 데 사용된다.

 

따라서 다음 Payload를 입력하면,

administrator'--
 

 

기존 SQL Query는 개념적으로 다음과 같이 변경된다.

SELECT * FROM users
WHERE username = 'administrator'--'
AND password = 'test'
 

 

-- 이후의 내용이 주석 처리되면서 다음 조건은 실행되지 않는다.

AND password = 'test'
 

 

결과적으로 실제 인증 조건은 사실상 다음과 같이 남게 된다.

SELECT * FROM users
WHERE username = 'administrator'
 

 

즉, administrator 계정의 존재 여부만 확인하게 되고 비밀번호 검증 과정은 수행되지 않는다.


6. 결과 확인

SQL Injection Payload를 적용한 상태에서 로그인 요청을 서버로 전송하였다.

그 결과 정상적인 비밀번호를 입력하지 않았음에도 administrator 계정으로 로그인이 성공하였다.

 

로그인 이후 화면에서는 현재 로그인된 사용자 정보가 다음과 같이 표시되는 것을 확인할 수 있다.

administrator
 

또한 Lab 상태가 Solved로 변경되면서 실습이 정상적으로 완료되었다.

 

즉,

AND password = '...'
 

조건이 --에 의해 주석 처리되면서 비밀번호 검증이 무력화되었고, 그 결과 비밀번호를 알지 못하는 상태에서도 관리자 계정으로 인증할 수 있었다.


7. 핵심 정리

이번 실습의 핵심은 로그인 입력값이 SQL Query에 직접 사용될 경우 SQL Injection을 이용해 인증 로직 자체를 변경할 수 있다는 점이다.

 

정상적인 로그인 Query가 다음과 같다면,

SELECT * FROM users
WHERE username = 'administrator'
AND password = 'password'
 

 

username에 다음 값을 입력하였다.

administrator'--
 

 

각 부분의 의미는 다음과 같다.

  • administrator : 로그인을 시도할 관리자 계정
  • ' : 기존 문자열 종료
  • -- : 이후 SQL Query를 주석 처리

 

결과적으로 SQL Query는 다음과 같은 형태가 된다.

SELECT * FROM users
WHERE username = 'administrator'--'
AND password = 'test'
 

 

따라서 비밀번호를 확인하는 다음 조건이 실행되지 않는다.

AND password = 'test'
 

 

최종적으로 서버는 사실상 다음 조건만 확인하게 된다.

username = 'administrator'
 

이를 통해 비밀번호 인증을 우회하여 administrator 계정으로 로그인할 수 있었다.

 

이번 Lab은 이전 상품 조회 SQL Injection 실습과 달리 OR 1=1을 사용해 전체 조건을 참으로 만드는 방식이 아니라, --를 이용해 비밀번호 검증 조건 자체를 주석 처리하는 것이 핵심이다.

 

 

+ Recent posts