본문 바로가기

웹 개발

[3주차] 가능한 로그인 로직 연구 - 식별과 인증

식별

사용자가 본인의 신원정보를 밝히고 확인하는 행위 ( ID )

 

인증 (Authentication)

보호된 리소스에 접근하는 것을 허용하기 이전에 등록된 유저의 신원을 입증 (validating)하는 과정

 

 

---------------------------------------------------------------------------------------------------------------------------------------------

 

우선, 로그인에 관해 배울 때 처음 보게되는 ID = 'BOKYU' and PWD = '1111' 같은

지정형 로그인 로직은 패스하도록 하자 너무 간단하기도 하고 사실 분석할 건덕지가 없으니..!

 

 

 

1 ) 식별 / 인증 동시

SELECT * FROM user WHERE id = '$_POST["id"]' and pass = '$_POST["pwd"]'

WHERE ~ AND ~ 는 id와 password가 같아야만 질의문을 실행하게 된다.

 

연산자에서 or는 둘 중에 하나만 맞아도 조건이 참이 되지만

and 는 둘 다 맞아야만 참을 반환하는 일종의 인증 역할을 수행할 수 있다.

 

인증을 받아야만 DB에서 데이터를 꺼내는 식별이 가능하기 때문에 식별 / 인증이 동시에 진행된다고 할 수 있다.

 

<?php
//세션 시작
session_start();

//MySQL DB연결
$con = mysqli_connect('localhost','root','1234','test');
$input_id = $_POST['id'];
$input_pw = $_POST['pw'];

//아이디가 있는지 없는지 검사
//식별-인증 구간
$query = "SELECT * FROM user WHERE username='".$input_id."' AND password='".$input_pw."'";
$result = $con -> query($query); //쿼리문을 데이터베이스에 적용시키는 코드
$row = mysqli_fetch_assoc($result);
if($row != 0){
    $_SESSION['is_login']=true;
    $_SESSION['id']=$input_id;
  // 이하 생략
  
  }

}

 

 

2 ) 식별 / 인증 분리

 

처음 만들었던 로그인 형태가 바로 이 형태였다.

 

** SQL 질의문 형태

SELECT password FROM user WHERE username='';

 

SELECT username FROM user WHERE username='';

 

식별 과정에서 입력한 id 만을 이용해 DB를 불러온다.

password를 불러오는지, username을 불러오는지 두 가지 형태가 있을 수 있다.

 

전에 만들어놓은 login_proc.php의 코드와 똑같진 않지만

 

식별 / 인증 분리의 기본적인 형태로 한번 수정해보았다

 

 

  login_proc.php

<?php
//세션 시작
session_start();

//MySQL DB연결
$con = mysqli_connect('localhost','root','1234','test');
$input_id = $_POST['id'];
$input_pw = $_POST['pw'];

//아이디가 있는지 없는지 검사
//식별 구간
$query = "SELECT password FROM user WHERE username='$input_id'";
$result = $con -> query($query); //쿼리문을 데이터베이스에 적용시키는 코드

if (mysqli_num_rows($result)>=1) {
  $row = mysqli_fetch_assoc($result);
  //인증 구간
  if($row['id']==$input_id){
    $_SESSION['is_login']=true;
    $_SESSION['id']=$input_id;
  // 이하 생략
  
  }

}

 

 

 

mysqli_num_rows 를 통해 실행한 쿼리문이 값이 있는지를 체크하고 1이상이면 연관배열을 생성한다.

 

이걸 할 수 있는 함수는 3가지가 있습니다.

  • mysql_fetch_assoc : 문자열 인덱스 배열로 변환
  • mysql_fetch_row : 숫자형 인덱스 배열로 변환
  • mysql_fetch_array : $result_type 의 결과에 따라 두 가지 타입의 배열을 반환하거나 동시에 반환할 수 있습니다.   

          ex ) mysql_fetch_array ($result MYSQL_BOTH) 이렇게 쓰면 두 가지 타입을 동시에 반환한다.

 

이 함수들은 또한 데이터를 읽은 후 함수 내부의 포인터를

증가시켜 다시 호출될 때 다음 행을 읽을 수 있도록 합니다.

 

 

 

3 ) 식별 / 인증 동시 + 개행

 

1번과 동일하지만 개행 처리가 되어 있어 SQL Injection 시에 주석 처리가 먹히지 않는다.

SELECT id,pass FROM user WHERE id = ''
\n AND pass = '';

 

4 ) 식별 / 인증 동시 + Hash

 

** SQL 질의문 형태

SELECT id,pass FROM user WHERE id = '' AND pass = md5('');

md5는 괄호 안의 문자열을 md5의 규칙에 따라 해시화하는 함수인데,

 

주로 쓰는 함수로 이것 말고도 SHA256도 있다.

 

SELECT id,pass FROM user WHERE id = '' AND pass = sha256('');

나머지는 해시함수가 들어가는 것 외에 1번과 동일하다.

 

login_proc.php

<?php
//세션 시작
session_start();

//MySQL DB연결
$con = mysqli_connect('localhost','root','1234','test');
$input_id = $_POST['id'];
$input_pw = $_POST['pw'];

//아이디가 있는지 없는지 검사
//식별-인증 구간
$query = "SELECT username,password FROM user WHERE username='".$input_id."' AND password='".md5($input_pw)."'";
$result = $con -> query($query); //쿼리문을 데이터베이스에 적용시키는 코드
$row = mysqli_fetch_assoc($result);
if($row != 0){
    $_SESSION['is_login']=true;
    $_SESSION['id']=$input_id;
  // 이하 생략
  
  }

}

 

5 ) 식별 / 인증 분리 + Hash

 

식별 / 인증 분리인 2번의 형태와 같지만 인증에서 해시함수가 들어가는 차이가 있다.

<?php
//세션 시작
session_start();

//MySQL DB연결
$con = mysqli_connect('localhost','root','1234','test');
$input_id = $_POST['id'];
$input_pw = $_POST['pw'];

//아이디가 있는지 없는지 검사
//식별 구간
$query = "SELECT username FROM user WHERE username='$input_id'";
$result = $con -> query($query); //쿼리문을 데이터베이스에 적용시키는 코드

if (mysqli_num_rows($result)>=1) {
  $row = mysqli_fetch_assoc($result);
  //인증 구간
  if($row['password']==md5($input_pw)){
    $_SESSION['is_login']=true;
    $_SESSION['id']=$input_id;
  // 이하 생략
  
  }

}

-------------------------------------------------------------

각 CASE 별 우회법

만들어 놓은 서버에서 테스트해보았다.

 

1 ) 식별 - 인증 동시 CASE

bg5294' # 이나

'  (작음 따옴표)

또는

bg5294' or 1=1 #

bg5294' or '1'='1 을 입력하면 로그인 우회가 가능하다.

 

 

2 ) 식별 - 인증 분리 CASE

 

식별 - 인증 분리 case에서는 SQL 문 뒤를 주석처리 해봤자 비밀번호 인증 부분이 해결되지 않으므로

union 을 쓰는 것이 좋다.

 

작은 따옴표로 짝을 맞추고 x' union select '1','2' # 이런 식으로 입력하면

각 db의 첫 인덱스에 1, 두번째 인덱스에 2 라는 글자가 새로 생성된다.

 

이를 이용해 id를 알고 있지만 비밀번호는 모른다고 할 때

x' union select 'bg5294','1234' # 이라고 입력하는 것이다.

그럼 순간적으로 bg5294 아이디에 1234 라는 비밀번호를 가진 계정으로 인식되어

로그인이 된다.

내 페이지에는 현재 어떤 계정으로 로그인되었는지 나타내는 기능을 추가해뒀는데,

 

union을 포함한 sql문이 통으로 아이디로 인식되어 있는 것을 볼 수 있다.

 

내가 입력한 코드가 id 변수에서는 x' union ~~ 이지만 sql문으로 들어갔을 때 질의문으로 바뀐다는 것을 알 수 있다.

근데 이건 앞 select 문과 뒤 select 문이 타입이 같게 맞춰놨기 때문이다. 

 

이런 식으로 모든 컬럼을 불러오게 로직이 짜여져 있다면 컬럼의 수가 몇개인지, 어떤 타입으로 이루어져있는지,

존재는 하지만 출력되진 않는 컬럼은 없는지를 판단해야한다.

 

 

 

현재 내 DB는 첫번째 컬럼이 id, 두번째 컬럼이 username 이런 식으로 짜여있고

 

컬럼의 수는 총 6개이다.

 

이는 x' order by 7 # 구문을 이용하면 아무 창도 뜨지 않는 오류를 보고 판단할 수 있다.

 

6 # 까지는 에러 메시지가 뜨므로 조건문을 통과했다는 것을 알 수 있다.

아무것도 안뜸

 

그래서 순서에 맞게 x' union select '1','bg5294','1234','3','4','5' # 이라고 입력 후,

비밀번호에 1234를 입력했더니 로그인이 되었다.

 

3 ) 식별 - 인증 동시 + 개행

주석을 포함한 SQL Injection 구문은 로그인 우회가 안된다.

 

bg5294' or '1'='1 처럼 작음 따옴표로 짝은 맞춘 구문만이 로그인 우회가 가능하다.

 

위 코드가 sql 질의문이 들어갔다고 생각하면

select username, password from user WHERE id=bg5294' or '1'='1'

and password = '아무거나';

 

초록색으로 표시한 부분이 내가 입력한 부분인데,

연산자 or 와 and 중 and의 연산순위가 높기 때문에

select username, password from user WHERE id=bg5294' or ('1'='1'

and password = '아무거나') ;

이렇게 먼저 실행되고 비밀번호 부분이 false가 나온다. 하지만 그 앞에 or가 있기 때문에 최종 조건이

true가 된다.

 

4 ) 식별 - 인증 동시 + HASH

 

입력 란에 bg5294' # 을 입력해서 해시함수 부분을 주석 처리하면 비밀번호와 관계없이

인증을 우회할 수 있다.

SELECT username,password FROM user WHERE id = 'bg5294' #' AND pass = md5('1234')

 

 

5 ) 식별 - 인증 분리 + HASH

만약 DB가

SELECT username, password FROM user WHERE id=''; 

인 형태라고 해보자.

 

그럼 id에 밑 코드를 대입한다.

x' union SELECT 'bg5294','해시된 주입 비밀번호값' #

해시된 주입 비밀번호값은 내가 만약 1234로 로그인을 할거라면,

 

1234를 md5의 규칙대로 해시화한 값인 81DC9BDB52D04DC20036DBD8313ED055 가 들어가야 한다는 뜻이다.

 

그 값은 구글링을 통해 사이트에서 쉽게 얻어낼 수 있다.

 

그 후, 로그인 창에서 비밀번호 입력란에 1234를 입력하면 1234가 변수로 지정되고 그 값이 함수 안으로 들어가

순간적으로 데이터베이스에 저장된 해시값 비밀번호와 맞아떨어지면서 로그인 우회가 가능해진다.

 

직접 개발한 페이지에서 시험해보자