1. SQL Injection 이란?

  sql injection 이란 해커가 애플리케이션의 db에 악의적인 의도를 가지고 공격자가 원하는 sql query가 실행되도록 하는 흔한 공격 방법이다. 주로 유저로부터 받은 데이터를 필터링, 이스케이핑 하지 못했을 때 발생한다. 때문에 개발자는 기본적으로 유저로부터 받은 인풋 값을
신뢰하지 않아야 한다.


2.  SQL Injection 공격의 종류

  2 - 1) 인증 우회
    보통 유저 인증을 위한 로그인 페이지에서 주로 행해진다. sql query에 논리적 연산 오류를 이용해서 로그인 인증 쿼리문이 무조건
    true 값이 나오도록 하여 인증을 무력화 시키는 원리다.
    ex) id 와 pw에 'or 1=1' 입력

  2 - 2) 데이터 노출
    타겟 시스템의 주요 데이터 탈취를 목적으로 행해지는 방식이다.
    시스템의 에러는 개발자에게 디버깅의 이로움을 주지만 오히려 해킹의 위험이 될 수도 있다.
    해커가 악의적인 sql query로 error를 유발시키고 그 error에서 단서를 얻어 더 위험한 공격을 감행할 수도 있다.
    그렇기 때문에 에러 페이지 또는 메세지가 노출되면 안된다.


3. 방어 방법

  3 - 1) 유저의 인풋을 필터링 및 이스케이핑 하자!
  3 - 2) sql 관련 에러 메세지를 유저에게 절대 보이지말자!
  3 - 3) 비밀번호의 경우 반드시 유저로부터 받은 데이터를 바로 저장하지말고 암호화해서 저장하자
  3 - 4) ORM을 사용해서 반드시 ORM에서 제공하는 api를 이용해서 sql query가 내부적으로 실행되도록 하자

'Backend > Database' 카테고리의 다른 글

정규화  (0) 2021.06.03
Transaction  (0) 2021.05.10

1. 정규화란?

  데이터 중복을 피하고 삽입/삭제/갱신 이상 현상을 방지하기 위해 데이터를 분해하는 과정!

  정규화는 제 1 정규화 ~ 제 6 정규화 까지 여러 과정이 존재하지만, 실무에서는 대체로 1 ~ 3 정규화까지의 과정을 거치게 된다.

 

2. 제 1 정규형

  1 정규화를 만족시키면 제 1 정규형이 된다.

 

1정규형 위반

  보다시피 연락처 칼럼에 두 가지 값이 들어가 있다. 즉 모든 칼럼은 오직 하나의 값만 가지도록 설계하는 것을 1 정규형이라한다.

 

 

3. 제 2 정규형

  

제 2정규형 위반

 

  현재 주문 테이블에선 고객 아이디와 주문번호가 하나의 PK로 구성되어 있다.

  하지만 고객명이나 고객등급 칼럼의 경우 고객아이디에만 함수적 종속성을 가진다.

  즉 주문번호에는 함수적 종속성을 가지지 않는다. 따라서 해당 테이블은 아래와 같이 분리될 수 있다.

 

 

4. 제 3 정규형

 

제 3 정규형 위반

 

  현재 고객 테이블에서 직업명 컬럼은 직업코드에 종속되어있다. 따라서 이런 경우 제 3 정규형에 위반 사례이다.

  제 3 정규형을 만족하려면 아래와 같이 테이블을 나누어야 한다.

 

  이때 고객 엔터티에서 직업코드 컬럼은 FK가 될 것이다.

 

5. 정규화와 성능

  정규화를 수행해서 조인이 발생하게 되더라도 효율적인 인덱스 사용을 통해 조인 연산을 수행하면 성능상 단점은 거의 없다.

  정규화를 수행해서 소량의 테이블이 생성된다면 소량의 테이블을 먼저 읽어 조인 연산을 수행하면 되므로 성능상 유리할 수 있다.

  정규화가 제대로 되지 않으면 동일한 종류의 속성을 여러 개가 있을 수 있고 과다한 인덱스가 만들어질 수 있는데 정규화를 한다면 하나의 인덱스만 만들어도 될 것이다.

 

 

 

'Backend > Database' 카테고리의 다른 글

SQL Injection  (0) 2021.06.18
Transaction  (0) 2021.05.10

1. Transaction

트랜잭션이란 질의(query)를 하나의 묶음 처리해서 만약 중간에 실행이 중단됐을 경우,

처음부터 다시 실행하는 Rollback을 수행하고, 오류없이 실행을 마치면 commit을 하는 실행 단위를 의미한다.

즉, 한 번 질의가 실행되면 질의가 모두 수행되거나 모두 수행되지 않도록 하는 매커니즘이다.

 

예를 들어, 친구에게 10,000원을 송금하는 상황을 가정해보자.

친구에게 송금을 한다면, 나의 계좌에서 10,000원이 줄고 친구의 계좌에 10,000원이 증가 되어야 하는데,

알 수 없는 오류로 인해 나의 계좌에선 10,000원이 줄었지만 친구 계좌에는 10,000원이 증가되지 않는다면 어떻게 될까?

매우 곤란한 상황일 것이다.

 

이러한 경우가 생기지 않도록 중간에 오류가 발생하면 다시 처음부터 송금을 하도록 하는 것이 rollback이다.

오류 없이 정상적으로 송금이 됐다면 정상적으로 실행이 끝났으므로 commit을 한다.

즉, 송금 과정이 하나의 트랜잭션이다.

 

참고로 트랜잭션을 작업수행의 논리적 단위라고 했는데, DBMS의 성능은 TPS : ( Transaction per second )로 측정한다.

 

 

트랜잭션의 사용 이유는 다음과 같다.

트랜잭션은  하나의 DB 서버에 여러 개의 클라이언트가 동시에 액세스 하거나 응용프로그램이 갱신을 처리하는 과정에서 중단될 수 있는 경우 등 데이터 부정합을 방지하고자 할 때 사용한다.

부정합이 발생하지 않으려면 프로세스를 병렬로 처리하지 않도록 하여 한 번에 하나의 프로세스만 처리하도록 하면 되는데, 이는 효율이 너무 낮다.

즉, 병렬로 처리할 수 밖에 없는 현실적인 상황으로 인해 부정합을 방지하고자 트랜잭션을 사용하는 것이다.

 

트랜잭션에서 중요한 것은 스케줄 관리입니다.

스케줄을 잘못 짜게 되면, 데드락에 빠지게 되는데 이와 관련된 주제가 뒤에서 살펴볼 2PL이다.

 

 

2. 특성

트랜잭션에는 아래와 같이 4가지의 특성이 있다.

4가지 특성의 앞 글자만 따서 ACID 특성이라 한다.

  • 원자성 ( Atomicity )
    • 하나의 트랜잭션이 부분적으로 실행되거나 중단되지 않는 것을 보장하는 것을 말한다.
    • 즉, All or Nothing의 개념으로서 작업 단위를 일부분만 실행하지 않는다는 것을 의미한다.
  • 일관성 ( Consistency )
    • 트랜잭션이 성공적으로 완료되면 일관적인 DB상태를 유지하는 것을 말한다.
    • 여기서 말하는 일관성이란, 위의 송금 예제에서 금액의 데이터 타입이 정수형(integer)인데, 갑자기 문자열(string)이 되지 않는 것을 말한다.
  • 격리성 ( Isolation )
    • 트랜잭션 수행시 다른 트랜잭션의 작업이 끼어들지 못하도록 보장하는 것을 말한다.
    • 즉, 트랜잭션끼리는 서로를 간섭할 수 없다.
  • 지속성 ( Durability )
    • 성공적으로 수행된 트랜잭션은 영원히 해당 상태가 반영이 되는 것을 말한다.
    • 성공적인 트랜잭션 후에 commit을 하면 현재 상태는 영원히 보장된다.

이제부터 트랜잭션이 각 특성들을 어떻게 보장하고 있는지 알아보도록 하자.

 

 

3. 원자성 보장

트랜잭션에서 원자성은 수행하고 있는 트랜잭션에 의해 변경된 내역을 유지하면서, 이전에 commit된 상태를 임시 영역에 따로 저장한다.

즉, 현재 수행하고 있는 트랜잭션에서 오류가 발생하면 현재 내역을 날려버리고 임시 영역에 저장했던 상태로 rollback 하는 것이다.

 

가장 최신의 데이터들이 임시로 저장되는 영역을 롤백 세그먼트(rollback segment)라고 하며,

현재 수행하고 있는 트랜잭션에 의해 새롭게 변경되는 내역을 테이터베이스 테이블이라고 한다.

다시 말하면, 트랜잭션의 원자성은 롤백 세그먼트에 의해 보장된다고 할 수 있다.

 

그런데 오류가 발생하면 rollback을 하는데, 트랜잭션의 길이가 길어지면 어떻게 될까?

확실하게 오류가 발생하지 않는 부분도 다시 처음부터 작업을 수행해야 한다.

따라서 확실한 부분에 대해서는 rollback이 되지 않도록 중간 저장 지점인 save point를 지정할 수 있다.

save point를 지정하면 rollback 할 때 save point 이전은 확실하다 간주하고 그 이후부터 진행하게 된다.

 

 

4. 일관성 보장

트랜잭션에서 일관성은 트랜잭션 수행 전, 후에 데이터 모델의 모든 제약 조건(기본키, 외래키, 도메인, 도메인 제약조건 등)을 만족하는 것을 통해 보장한다.

 

예를 들어, Movie와 Video 테이블이 있을 때 Video 테이블에 Movie 테이블의 primary key인 movie_id가 외래키로 존재한다고 가정하자.

만약 movie_id의 제약조건이 Movie 테이블에서 변경되면, Video 테이블에서도 movie_id 가 변경되어야 한다.

한 쪽의 테이블에만 데이터 변경사항이 이루어지면 안되는 것이죠.

 

그렇다면 어떻게 트랜잭션은 일관성을 보장할까?

어떤 이벤트 조건 발생했을 때, 트리거( Trigger )를 통해 보장한다.

트리거는 "방아쇠"인데, 트리거에 따라 데이터베이스 시스템이 자동적으로 수행할 동작을 명시하는데 사용된다.

 

 

 

5. 고립성 보장

트랜잭션이 고립성을 보장하는 방법에 대해 이해하기 위해서는 병행 트랜잭션에 대해 먼저 알아야 합니다.

 

1) 병행 처리 ( concurrent processing )

CPU가 여러 프로세스를 처리하는 것처럼, 트랜잭션에 정해진 시간을 할당해서 작업을 하다가 부여된 시간이 끝나면 다른 트랜잭션을 실행하는 이런 방식으로 트랜잭션들을 조금씩 처리하는 것을 말한다.

그런데 이렇게 되면 많은 트랜잭션들이 조금씩 처리되는 과정에서 공통된 데이터를 조작할 수도 있는데, 이 경우 데이터가 혼란스러워 질 수 있다.

 

예를 들어, A 트랜잭션에서 x라는 데이터를 100으로 설정한 후 시간이 만료되어 B 트랜잭션으로 넘어갔다고 가정해보자.

B 트랜잭션에서는 x 데이터에 -50 연산을 해서 저장을 했을때, 시간이 만료되어 다시 A 트랜잭션이 실행될 경우 x 데이터의 값은 50이 될 것이다.

이렇게 트랜잭션이 조금씩 수행될 때, 공통된 데이터가 다른 트랜잭션에 의해 방해되면 안된다.

당연한 논리인게 내가 아닌 제 3자로 인해 의도된대로 데이터의 수정이 이루어지지 않았기 때문이다.

이와 같이 트랜잭션의 간섭이 일어날 경우 갱신분실, 오손판독, 반복불가능, 팬텀문제 등 여러 문제점들이 발생할 수 있다.

 

 

 

2) 고립성 보장

병행처리 과정에서 트랜잭션의 고립성이 왜 보장되어야 하는지를 알게되었다.

 

그러면 고립성을 어떻게 보장할 수 있을까요?

OS의 세마포어(semaphore)와 비슷한 개념으로 lock & excute  unlock을 통해 고립성을 보장할 수 있다.

즉, 데이터를 읽거나 쓸 때는 문을 잠궈서 다른 트랜잭션이 접근하지 못하도록 고립성을 보장하고, 수행을 마치면 unlock을 통해 데이터를 다른 트랜잭션이 접근할 수 있도록 허용하는 방식이다.

 

트랜잭션에서는 데이터를 읽을 때, 여러 트랜잭션이 읽을 수는 있도록 허용하는 shared_lock을 한다.

즉, shared_lock은 데이터 쓰기를 허용하지 않고 오직 읽기만 허용한다.

 

또한 데이터를 쓸 때는 다른 트랜잭션이 읽을 수도 쓸 수도 없도록 하는 exclusive_lock을 사용한다.

그리고 읽기, 쓰기 작업이 끝나면 unlock을 통해 다른 트랜잭션이 lock을 할 수 있도록 데이터에 대한 잠금(lock)을 풀어준다.

 

그런데 lock과 unlock을 잘못 사용하면 데드락(deadlock)상태에 빠질 수 있다.

모든 트랜잭션이 아무것도 수행할 수 없는 상태가 되는 것이죠.

 

deadlock

 

 

3) 2PL 프로토콜 ( 2 Phase Locking protocol )

당연히 데드락이 걸리면 안되므로, 어떤 규칙에 의해서 고립성을 보장해야 한다는 2PL 프로토콜이 연구되었다.

2PL 프로토콜이란 여러 트랜잭션이 공유하고 있는 데이터에 동시에 접근할 수 없도록 하기위한 목적을 가진 프로토콜이다.

 

이름 그대로 2가지 단계의 locking이 존재하는데, 한 가지는 growing phase이고 다른 한 가지는 shrinking phase입니다.

상승 단계란 read_lock , write_lock을 의미하고, 하강 단계란 unlock를 의미한다.

 

2PL 프로토콜은 상승 단계와 하강 단계와 섞이면 안된다는 것을 의미한다.

즉, lock과 unlock이 번갈아 수행되지 않고 lock이 쭉 수행된 후에 unlock이 쭉 수행되어야 한다는 것이 이 프로토콜이다.

 

2PL

 

정리하면 성능을 위해 병행처리를 해야하는데, 트랜잭션의 고립성을 보장하기 위해서는 2PL을 사용해야 한다는 것이다.

 

참고로 locking을 하는 방법에도 두 가지가 있다.

  • 보수적 locking ( conservative locking )
    • 트랜잭션이 시작되면 모든 lock을 얻는 방식으로서, 데드락이 발생하지 않지만 병행성이 좋지 못함
  • 엄격한 locking ( strict locking )
    • 트랜잭션이 commit을 만날 때까지 lock을 갖고 있다가 commit을 만날때 unlock을 하는 방식으로 데드락이 발생하지만 병행성이 좋음
    • 일반적으로 병행성이 좋은 strict 방식을 사용.

 

더 자세한 내용은 여기를 참고하자!

 

 

*참고 : victorydntmd.tistory.com/129

'Backend > Database' 카테고리의 다른 글

SQL Injection  (0) 2021.06.18
정규화  (0) 2021.06.03

+ Recent posts