모의해킹 스터디 과제

모의해킹 스터디 3주차 과제: 로그인 케이스

whydontyoushovel 2024. 11. 5. 02:29

※ 3주차 과제

≫ 사전 작업

  • DB 정리하기
  • 케이스 나눌 파일 만들고 로그인 페이지랑 연결시키기

≫ 로그인 케이스 네 가지 만들기

  • 3주차의 완성본
  • 겪은 문제들

≫ 회원가입 insert문에 hash 데이터 추가하기

  • 3주차의 완성본
  • 겪은 문제들

 

 

 

♨ 사전 작업

 

DB 정리하기

로그인 케이스 4가지를 만들어야 한다.

 

1. 식별 (아이디), 인증(비번) 동시에 검증

2. 식별, 인증 따로 검증

3. 동시에 검증하면서 인증 데이터 해싱 적용

4. 따로 검증하면서 인증 데이터에 해싱 적용

 

    => 기존 평문 비밀번호만 있던 DB에 해시화된 비밀번호 컬럼 필요

 

㉮  컬럼 추가

ALTER TABLE registration_list ADD pass1 varchar(20);

ALTER TABLE [테이블명] ADD [컬럼명] [자료형(길이)];

 

 

㉯  값 추가

UPDATE registration_list SET pass1 ='평문 비밀번호' WHERE id='아이디';

UPDATE [테이블명] SET [컬럼명] = '넣을 데이터' WHERE [조건];

 

 

㉰  해시화할 컬럼 길이 수정 (SHA256)

해시 알고리즘 종류에 따른 컬럼 크기

ALTER TABLE registration_list MODIFY pass varchar(64);

ALTER TABLE [테이블명] MODIFY [컬럼명] [자료형(길이)];

 

 

㉱  컬럼 데이터 해시화

UPDATE registration_list SET pass = SHA2(pass, 256);

UPDATE [테이블명] SET [컬럼명] = SHA2(컬럼명, 알고리즘 종류);  

 

 

㉲  결과

pass가 해시화된 비밀번호를, pass1이 평문 비밀번호를 담고 있는 컬럼이다.

 

 

 

케이스 나눌 파일 만들고 로그인 페이지랑 연결시키기

  • 자바 스크립트 이용

로그인 페이지

<?php 
    session_start();

    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);
    error_reporting(E_ALL);
?>

<!DOCTYPE html>
<html>
    <head>
        <link href="./loginstyle.css" rel="stylesheet">
        <meta charset="UTF-8">
        <title>
            로그인 페이지
        </title>
    </head>

    <body>
        <div class="logo">
            <img src="./img/logo.png"/>
        </div>            
                <form method="post" action="" id="login">
                    <br><br>
                    <div class="input">
                        <div class="main-content">
                            <input type="text" name="id" placeholder="아이디" value="<?php echo htmlspecialchars($_POST['id'] ?? '')?>" class="main-content"/>
                            
                        </div>
                        
                        <div class="main-content">
                            <input type="password" name="passwd" placeholder="비밀번호"/>

                        </div>

                        <select name="case" id="case">
                            <option value="case1">식별+인증 동시</option>
                            <option value="case2">식별/인증 따로</option>
                            <option value="case3">동시 해싱</option>
                            <option value="case4">따로 해싱</option>
                        </select>


                        <div>
                            <input type="submit" value="로그인" class="button" name="submit"/>
                        </div>
                    </div>    
                    <br><span style='color:red' class="login_error"><?=isset($_SESSION['login_error'])? $_SESSION['login_error']:"";?></span>
                    <span style='color:red' class="login_error"><?=isset($_SESSION['empty_error'])? $_SESSION['empty_error']:"";?></span>
                    <span style='color:red' class="login_error"><?=isset($_SESSION['id_empty'])? $_SESSION['id_empty']:"";?></span>
                    <span style='color:red' class="login_error"><?=isset($_SESSION['pw_empty'])? $_SESSION['pw_empty']:"";?></span>
                    <?php 
                        unset($_SESSION['login_error']);
                        unset($_SESSION['empty_error']);
                        unset($_SESSION['id_empty']);
                        unset($_SESSION['pw_empty']);
                    ?>
                        <footer>
                            <hr>
                            <a href="#">X로 로그인하기</a>
                            <a href="./registration.php" target="_blank">회원가입</a>
                            <hr>
                        </footer>    
                </form>
            
        
    </body>
</html>

<script>
document.getElementById("login").onsubmit = function() {

    const selectedCase = document.getElementById("case").value // 케이스에 따라 다른 파일로 이동

    if (selectedCase === "case1") {
        this.action = "login_case1.php";   //식별+검증 동시
    } else if (selectedCase === "case2") {
        this.action = "login_case2.php";   //식별, 검증 따로
    } else if (selectedCase === "case3") {
        this.action = "login_case3.php";   //동시 + 해싱
    } else if (selectedCase === "case4") {
        this.action = "login_case4.php";   //따로 + 해싱
    }
};
</script>

 

 

사용된 자바스크립트에 대한 내가 이해한만큼의 해설

더보기
<script>
document.getElementById("login").onsubmit = function() {

    const selectedCase = document.getElementById("case").value // 케이스에 따라 다른 파일로 이동

    if (selectedCase === "case1") {
        this.action = "login_case1.php";   //식별+검증 동시
    } else if (selectedCase === "case2") {
        this.action = "login_case2.php";   //식별, 검증 따로
    } else if (selectedCase === "case3") {
        this.action = "login_case3.php";   //동시 + 해싱
    } else if (selectedCase === "case4") {
        this.action = "login_case4.php";   //따로 + 해싱
    }
};
</script>

 

1) document.getElementById("login").onsubmit = function() { 어쩌구~~ }

      => submit 눌리면 id 속성 값이 "login"인 태그에 접근해서 { 중괄호 안을 } 실행하라.

 

2) const selectedCase = document.getElementById("case").value

      => selectedCase라는 변수를 상수로 선언하고 id값이 "case"인 태그에 접근하여 선택된 값을 변수에 저장하라.

           변수를 상수로 저장한다는 것은 해당 변수에 저장될 수 있는 값이 정해지고부터는 수정이 불가하다는 뜻.

           해당 태그의 value는 case1, case2, case3, case4가 있음. 즉, 이 4개 제외하곤 변수에 저장 불가.

 

3) if문

      => selectedCase 변수에 저장된 값과 자료형이 "case1"과 완전히 같으면 "login_case1.php" 파일로 form에 입력된 값을 보내라. (form 태그의 action 속성과 비슷하게 동작) 

 

 

 

 

 

♨ 로그인 케이스 만들기

 

  • 3주차의 완성본

㉮  식별 + 인증 동시 (평문) (login_case1.php)

<?php 

//식별 + 인증 동시

session_start();

define('DB_SERVER', 'localhost');
define('DB_USER', '안알랴줌');
define('DB_PASS', '안알랴줌222');
define('DB_NAME', 'registration');

$db_conn = mysqli_connect(DB_SERVER,DB_USER,DB_PASS,DB_NAME);

if(!$db_conn) {
    echo "인증 오류";
} //디버깅 코드

$id = $_POST['id'];
$pw = $_POST['passwd'];

$select_sql = "SELECT * FROM registration_list WHERE id='$id' AND pass1='$pw'";

$result = mysqli_query($db_conn,$select_sql);

$row = mysqli_fetch_array($result);


if (empty($id)) {
    $_SESSION['id_empty'] = "아이디를 입력해주세요.";
    header("location: login.php");
    exit;
}  

if (empty($pw)) {
    $_SESSION['pw_empty'] = "비밀번호를 입력해주세요.";
    header("location: login.php");
    exit;
}    //입력값 없을 시 오류문 출력


if ($row) {

    header("location: index.html");
    exit;

} else {

    $_SESSION['login_error'] = "아이디 또는 비밀번호가 일치하지 않습니다.";
    header("location: login.php");
    exit;

}    // 식별+검증 동시


?>

 

입력받은 식별 데이터(id)와 인증 데이터(비밀번호)가 DB에 있는지 동시에 검증하고 있다.

있으면 index로 이동하고 없으면 경고문을 출력한다.

 

입력받은 데이터가 DB에 있는지 검증하는 마지막 if문에 if ($result)를 넣으면

엉뚱한 데이터를 넣어도 index페이지로 이동되었다.

쿼리 결과가 0과 같이 나오는 게 아닌 듯하다.

 

또한 위와 반대로 검증 if문을 위로 올리고 아이디, 비밀번호 미입력시 경고문 출력 부분을 아래로 내리면

빈 폼을 제출해도 경고문은 "아이디 또는 비밀번호가 일치하지 않습니다."만 나온다. 

 

 

 

㉯  식별, 인증 따로 검증 (평문) (login_case2.php)

<?php

위와 동일

	$select_sql = "SELECT * FROM registration_list WHERE id='$id'";

	$result = mysqli_query($db_conn,$select_sql);

	$row = mysqli_fetch_array($result);


    $user_pw = $row['pass1']; //평문 비밀번호의 컬럼명
    

    if (empty($id)) {
        $_SESSION['id_empty'] = "아이디를 입력해주세요.";
        header("location: login.php");
        exit;
    }  

    if (empty($pw)) {
        $_SESSION['pw_empty'] = "비밀번호를 입력해주세요.";
        header("location: login.php");
        exit;
    }    //입력값 없을 시 오류문 출력

    if ($pw == $user_pw) {
        header("location: index.html");
        exit;
    } else {
        $_SESSION['login_error'] = "아이디 또는 비밀번호가 일치하지 않습니다.";
        header("location: login.php");
        exit;
    }

?>

 

select문을 통해 입력받은 id와 같은 id값을 가진 행 데이터를 식별하고

해당 행에서 인증 데이터를 뽑아서 입력받은 인증 데이터와 같은지 비교하고 있다.

 

즉 식별과 인증을 분리시켜 진행하였다.

 

 

㉰  식별 + 인증(해시화) 동시 검증 (login_case3.php)

<?php

위와 같음

    $id = $_POST['id'];
    $pw = hash("sha256",$_POST['passwd']);  //입력받은 인증 데이터를 해시화하여 저장

    $select_sql = "SELECT * FROM registration_list WHERE id='$id' and pass='$pw'";


    $result = mysqli_query($db_conn,$select_sql);

    $row = mysqli_fetch_array($result);


    if (empty($id)) {
        $_SESSION['id_empty'] = "아이디를 입력해주세요.";
        header("location: login.php");
        exit;
    }  

    if (empty($pw)) {
        $_SESSION['pw_empty'] = "비밀번호를 입력해주세요.";
        header("location: login.php");
        exit;
    }    //입력값 없을 시 오류문 출력


    if ($row) {
        header("location: index.html");
        exit;   
    } else {
        $_SESSION['login_error'] = "아이디 또는 비밀번호가 일치하지 않습니다.";
        header("location: login.php");
        exit;
    }    // 식별+검증 동시
   
    
?>

 

select 문으로 아이디와 비밀번호를 동시에 검증하면서

이때 사용자에게 받은 비밀번호 값을 해시화하여 비교하고 있다.

 

 

㉱  식별, 인증(해시화) 따로 (login_case4.php)

<?php

위와 같음

    $id = $_POST['id'];
    $pw = $_POST['passwd'];

    $user_pw = $row['pass'];  //해시화된 비밀번호가 있는 컬럼명


    if (empty($id)) {
        $_SESSION['id_empty'] = "아이디를 입력해주세요.";
        header("location: login.php");
        exit;
    }  

    if (empty($pw)) {
        $_SESSION['pw_empty'] = "비밀번호를 입력해주세요.";
        header("location: login.php");
        exit;
    }    //입력값 없을 시 오류문 출력

    if (hash("sha256",$pw) == $user_pw) {
        header("location: index.html");
        exit;
    } else {
        $_SESSION['login_error'] = "아이디 또는 비밀번호가 일치하지 않습니다.";
        header("location: login.php");
        exit;
    }

?>

 

입력받은 아이디로 DB에서 해당 데이터 행을 뽑고 행에서 해시화된 인증 데이터 값을 뽑는다.

뽑은 값이 입력받은 인증 데이터 값을 해시화한 값과 같은지 비교한다.

 

이때 해시를 어디다가 하느냐에 따라 결과가 달랐는데,

해시를 입력받은 비번 값을 변수에 넣을 때 할 경우 폼에 비번을 입력하지 않고 제출하여도

"비밀번호를 입력해주세요."라는 경고문 대신 "아이디 또는 비밀번호가 일치하지 않습니다."라는 경고문이 출력되었다.

 

공란을 해싱하면 눈에 보이는 값이 나오지는 않을 것 같지만 서버 입장에서 어떤 값이라고 할만한 것이 나오는 듯하다.

 

 

 

*php에서 데이터 해싱하기*

형태 : hash("사용할 알고리즘", 적용할 변수나 데이터)

                        (sha256)          ($pw 혹은 $_POST['pw'] 등)

 

 

 

index.html로 잘 이동된다.

아이디를 입력하지 않았을 시 경고문

 

비밀번호를 입력하지 않았을 시 경고문

경고문도 잘 출력되는 모습이다.

 

 

 

  • 겪은 문제들

1)

폼 제출이 안돼!!

form에 입력받은 데이터를 적절한 파일로 보낼 자바스크립트에서

event.preventDefault(); 함수를 사용했었다. 폼 제출할 때 페이지 리로드하는 걸 막아준단다.

알고보니 이건 자바스크립트와 AJAX를 함께 사용할 때 페이지를 새로고침하지 않게 막아주는 역할을 한다고 한다. 쟤네끼리는 그렇게 로그인 처리가 가능한 듯. 나는 폼 제출한 걸 php로 받아야하니까 쓰면 걸림돌이다. 저거 지웠더니 잘 실행됐다.

 

...솔직히 아직 완전히 이해되진 않는다. 자바스크립트에 관심을 가져야겠다. 

 

 

 

 

회원가입 insert문에 hash 데이터 추가하기

 

  • 3주차의 완성본
<?php

세션 시작, db연동, 변수 할당


    $sql_select = "SELECT * FROM registration_list WHERE id='$regi_id'";
    
    $select_result = mysqli_query($db_conn, $sql_select);

    $row = mysqli_fetch_array($select_result); //SELECT문


    require_once('vali_check.php');
 
    $login_func = login_check1($regi_id,$row['id']);
    $login_func2 = login_check2($regi_pass,$regi_pass_ok); //유효성 검사


    
    if (empty($regi_id)) {
        $_SESSION['regi_id_empty'] = "사용하실 아이디를 입력해주세요.";
        header("location: registration.php");
    }
    if (empty($regi_pass)) {
        $_SESSION['pw_empty'] = "비밀번호를 입력해주세요.";
        header("location: registration.php");
    }
    if (empty($regi_pass_ok)) {
        $_SESSION['pw_ok_empty'] = "비밀번호를 확인해주세요.";
        header("location: registration.php");
    }
    if (empty($email)) {
        $_SESSION['email_empty'] = "이메일 주소를 입력해주세요.";
        header("location: registration.php");
    }
    if (empty($nick)) {
        $_SESSION['nick_empty'] = "사용하실 닉네임을 입력해주세요.";
        header("location: registration.php");
        exit;
    }   //입력 안하면 경고문 출력


  
    $regi_pass_hash = hash("sha256", $regi_pass);  //비번 해시화



    if ($login_func && $login_func2) {                                                                           //여기 sha2
            
        $sql_insert = "INSERT INTO registration_list(idx,id,pass,email,nickname,pass1) values(null, '$regi_id','$regi_pass_hash','$email','$nick','$regi_pass')";

        $insert_result= mysqli_query($db_conn, $sql_insert);

        if (!$insert_result) {
            die(mysqli_error($db_conn));
        }//디버깅 코드
    
        header("location: index.html"); //INSERT문 + 리다이렉트
        exit;
    } 

    if (!$login_func) {
        $_SESSION['id_error'] = "이미 사용 중인 아이디입니다.";
        header("location: registration.php");
            
    }

    if (!$login_func2) {
        $_SESSION['pw_error'] = "비밀번호가 일치하지 않습니다.";
        header("location: registration.php");
        exit;
    }
       
?>

 

insert문을 저장하기 전 사용자가 입력한 비밀번호를 해시화하여 변수에 저장하고 있다.

그 변수를 insert문에 사용했다.

 

 

<오류문 출력 테스트>

 

<회원가입 테스트>

성공할 데이터를 입력한 모습이다.
가입하기를 누른 결과 phpMyAdmin에 들어온 데이터의 모습이다. 다 가려놓고 할 말은 아니지만 잘 작동 중이다.

 

 

 

 

  • 겪은 문제들

1)

VScode에서 그거 그렇게 하는 거 아니래!!!

 $sql_insert = "INSERT INTO registration_list(idx,id,pass,email,nickname,pass1) values(null, '$regi_id','hash("sha256",$regi_pass)','$email','$nick','$regi_pass')";

 

위 코드처럼 values 값에서 해시화를 하도록 넣었었다. 

VScode에서 problem 창에 친절히 저거 잘못됐다고 알려주는 것을 볼 수 있었고

사이트에서도 실행이 안되었다.

 

데이터를 넣으면서 해시화하는 건 안되는 것 같아서 해시된 값을 변수에 넣어 처리하였다.

 

 

 

2)

insert문에 변수 추가했더니 실행이 안돼!!!!!

 

오타 문제였다.

$sql_insert = "INSERT INTO registration_list(idx,id,pass,email,nickname,pass1) values(null, '$regi_id','$regi_pass_hash,'$email','$nick','$regi_pass')";

위처럼 해싱 데이터에 작은 따옴표 하나를 빼먹었었다.

 

 

 

추가로 지금까지 디버깅 코드를 잘못 사용하고 있던 게 있었다.

if ($sql_insert) {
	die(mysqli_error($db_conn));
}

 

이 코드를 써야했는데 지금까지 "mysqli_error($db_conn)" 처럼 큰따옴표도 함께 넣고 있었다. 

 

 

 

 

 

 

 

 

아래는 재미도 감동도 없는 작은 해프닝의 기록이다. == 일기

더보기

여느때처럼 평화롭게 과제 흐름을 생각하던 나. 뭐가 필요한가 찾던 중 db에 있는 사용자들의 해시화된 비밀번호가 필요하다는 것을 깨닫는다.

 

아, 이미 있던 계정의 비밀번호도 암호화해야겠구나! 쉬워보이니 이것부터 해볼까?

암호화하는 SQL은 어떻게 쓰려나?

 

앗 찾았다^^!

 

한번 해보자.

 

어라 안되네. 아 필드 크기가 문제구나?

오케이 내가 쓸 SHA256은 적어도 64의 길이가 필요한가 보구나. 당장 바꿔야지

 

다시 한 번 SQL 입력하고

실행!

와 됐다~

 

어 근데 암호화 하기 전의 케이스는 어떻게 기록하지?

....평문으로 복구 못하는데..

 

그렇게 땅 파다가 다른 분이 로그인 케이스에 따라 로직(?)을 따로 만드시는 걸 보고 나도 네 개 따로 만들어야겠다 결심. 각각의 해킹 기법이 다르다면 따로 만드는 게 맞는 것 같다.

 

그래서 내 db는 암호화된 비번부터 나온다라는 이야기~