MySQL의 "Lock"을 이용한 DoS 공격 시나리오

Minseok Jo·2024년 5월 10일
post-thumbnail

동시성?

이번 주에 학교 운영체제 강의 시간에서 Concurrency(동시성)에 관한 내용을 배웠다.
동시성에 대해 간단하게 설명하자면 다음과 같다.

만약 다음 그림과 같이, 100만원이 들어있는 통장에 두 사람이 동시에 100만원을 출금하려한다면 어떻게 될까?

물론, 1분 1초 1미리초 오차하나 없이 동일하게 한다는 것은 거의 불가능하겠지만 이론상으로는 발생할 수 있는 상황이다.
이때 정상적인 동작이라면, 두명 모두한테 100만원이 출금되어서는 안될 것이다.

이렇듯 공유 자원에 대해 여러 사용자(스레드)가 동시에 접근하는 경우, 의도치 않은 실행결과가 나오지 않도록 보장하는 것이 "동시성" 의 개념이다.


웹 서버에서도 동시성 문제가 존재!

위 표를 보면 현재 사용되는 웹 서버에서도 동시성으로 인한 버그들이 여러건 존재함을 알 수 있다.
그렇다면 이러한 동시성으로 인한 버그를 막기 위해 어떤 대안방안들이 사용되고 있을까?


MySQL의 동시성 문제 해결방안

위의 예시 가운데 많이 사용되는 MySQL에 경우, 이러한 동시성 문제를 어떻게 해결하는지 찾아보았다.
찾아본 결과, MySQL은 DB Lock이라고 하는 개념을 사용하고 있었다.

DB Lock은 자물쇠의 개념으로 아래 사진처럼 표현될 수 있다.

위 그림처럼 다수의 사용자가 접근하는 공통자원(Item X) 영역 앞에 자물쇠(lock)를 두는 것이다.

??? : "그럼 이 자물쇠로 어떻게 하겠다는 것인데??"

공통 자원에 대해 어떤 작업(ex. 값 변경, 저장)들을 수행하고자 한다면 이 자물쇠를 잠그고, 그 작업을 수행한다.
작업을 수행하는 도중에 만약 다른 사용자가 값을 수정하려 시도하더라도 이 자물쇠가 잠겨져 있기 때문에 접근할 수 없게 되고, 이 자물쇠가 해제될때까지 대기하게 됨으로써 값이 동시에 변할 수 있는 만약의 사태를 방지하는 것이다.

자물쇠를 잠그고 작업을 수행하던 사용자가 작업을 다 끝마치고 나올때 자물쇠를 다시 풀어주게 되면, 그제서야 다른 사용자가 이 자원에 접근할 수 있게 되는 방식이다. (물론 다음 사용자 또한 자물쇠를 잠그고 들어간 후, 나올때 다시 자물쇠를 풀어주는 과정을 똑같이 수행한다.)


그렇다면 MySQL에서 Lock을 어떻게 잠금할까?

MySQL에서 일반적으로 사용하는 select문은 다음과 같다.

select * from table where id=1;

해당 구문에서 뒤에 FOR UPDATE만 추가해주면 select문을 수행하면서 Lock을 걸게된다.

select * from table where id=1 FOR UPDATE;

위의 구문(select FOR UPDATE)을 수행하게 되면 Lock을 획득하게 되고,
해당 세션이 COMMIT; 을 실행하기 전까지 다른 세션들은 대기하게 되는 것이다.


어? 잠깐만.. 이거 공격으로 사용할 수 있을거 같은데?

만약 내가 Lock을 가져가버리고 풀어주지 않는다면??
다른 사용자들은 원하는 동작을 수행하지 못하고, 계속 기다리기만 해야되는건가?

그렇다면 이것이 공격 시나리오로 이어질 수 있을까?

실험을 위해, 이전에 제작해두었던 웹사이트를 바로 켜보았다.


공격 시나리오 테스트

시나리오를 정리하자면 다음과 같다.

  1. SQL Injection 취약점 발견
  2. FOR UPDATE 명령 삽입 및 잠금(Lock) 획득
  3. 다른 사용자들은 데이터에 대한 접근이 차단되어, 서비스 이용이 제한
    → SQL Injection 취약점으로부터 서비스 거부(DoS) 공격 수행

테스트에 사용될 웹은 다음과 같습니다.

저의 경우 웹 페이지중 "QNA 게시글" 페이지에서 실습을 해보았습니다.



1. SQL Injection 취약점 확인

참[and 1=1] / 거짓[and 1=2] 조건 실행 결과

→ 게시글 view 페이지의 인덱스 번호에서 SQL Injection 동작 확인


2. FOR UPATE; 명령 실행 및 Lock 획득

// 실행되는 쿼리문은 다음과 같다.
select * from (테이블) where num = 1 or 1=1 for update;

그런데 이때 for update; 만 붙여줘도 Lock이 걸리는데 or 1=1 까지 입력해준 것에는 이유가 있다.

num 값에 그냥 "1 for update" 만 삽입하는 경우 쿼리문이
select * from (테이블) where num = 1 for update;
위 처럼 되어버려, num값이 1인 데이터에 대해서만 Lock이 적용된다.

따라서, 테이블 전체에 Lock을 걸어주기 위해 or 1=1 조건을 추가해준 것이다.



3. 타 이용자들은 해당 테이블 접근이 차단되며, 서비스 이용이 제한

<타 이용자의 시점>

자신의 게시글 내용 수정 시도

내용 수정 요청이 전달되지 않게 된다.
────────────────────────────────────────────────────

게시글 작성 페이지에서도 마찬가지로, 요청이 전송되지 않게 된다.
→ 게시글 수정 & 작성 모두 아까 Lock을 걸어둔 테이블에 접근하려하기 때문

결론

구상해본 시나리오를 직접 실습해본 결과, 동작 가능함을 확인할 수 있었습니다.

그런데, FOR UPDATE; 를 비롯한 LOCK IN SHARE MODE; 등의 Lock 명령어를 수행하기 위해서는 약간의 조건이 필요합니다.
→ 트랜섹션을 시작할 수 있어야 하고, 데이터베이스의 AUTO COMMIT 옵션이 해제 되어있어야 합니다.

저의 경우 실습을 위해 phpMyAdmin, DBeaver 툴을 사용해보았는데 두 경우 모두 AUTO COMMIT 옵션이 True로 설정되어있어, 이를 해제하고 실습을 진행하였습니다.
→ 거의 대부분 위처럼 Lock으로 인한 과도한 대기를 방지하고자 자동으로 Lock을 해제해주는 AUTO COMMIT을 사용한다고 합니다!

이처럼 동작하기 위해서 전제조건이 필요한 공격방식이었지만, 실습을 통해 구상해본 시나리오가 실제로 가능함이 확인할 수 있었던 경험이었던 것 같습니다!
이상으로 게시글 작성을 끝마치며, 앞으로도 다양한 주제로 실험글을 작성해보겠습니다 !


0개의 댓글