kjh00n의 기록저장소

데이터 검증 본문

어플리케이션 보안 운영

데이터 검증

kjh00n 2025. 1. 3. 11:52

홈페이지 분석

192.168.50.50 (홈페이지 구성)

 

index.php → style_contents.css → head.php → style_head.css

 

index.php(메인)

head.php(메뉴)

→ index.php

→ board/board_list.php

→ member/login.php

→ member/register.php

 

board/board_list.php(게시판 글 리스트)

→ board_view.php → board/board_view.php?num=???

num 값에 따라 게시글 내용이 달라짐

 

board_view.php (분석) (게시판)

사용자가 입력한 게시글 번호를 확인, 조회, 출력

(DB 접속, SELECT SQL문 작성, 질의, 결과 출력)

 

→ 조회 성공 시에는 게시글 출력이 되지만 조회 실패 시에는 아무것도 출력되지 않는다.

성공과 실패했을 때의 코드가 서로 비슷하다라고 판단할 수 있음

 

유추하기로는 조회 성공이든 실패이든 같은 코드가 동작하는 것 같다~ (해커입장)

SELECT ??? FROM ??? WHERE ??? = num ??? (←라고 해커가 유추할 수 있음)

 

login.php (분석) (로그인)

입력받는 값이 2개인 Page

ID : user_id라는 이름으로 전송

PW : user_pw라는 이름으로 전송

POST방식으로 member/login_check.php에 요청

user_id=???&user_pw=??? → http request body 로 해커가 유추가 가능함

추가적으로 전달되는 Data는 없다는 것을 확인할 수 있음

 

login_check.php는 무슨 역할을 하는 녀석인가?

얘는 로그인 성공 / 실패 여부를 확인하고 결과를 출력해주는 Page

(DB 접속, SELECT문 작성, 조회, 결과 출력)

SELECT ??? FROM ??? WHERE ???=user_id AND ???=user_pw ?? (라고 해커가 유추)

결과 출력 = 성공일 때 출력과 실패일 때 출력이 다르다 (코드가 서로 다르다)

얘는 if함수를 사용했을 것이라고 유추가 가능함

 

if(SELECT문의 결과)

성공 → alert(경고창) 출력 → index.php 이동

else

실패 → alert(경고창) 출력 → login.php 이동

 

결과적으로 사용자가 입력한 ID / PW에 따라 SELECT문의 결과가 달라지고

그 결과에 따라 로그인 성공 / 실패가 결정된다 → ID / PW에 따라 성공 / 실패

 

 

해커가 수집한 정보 ↓

Server = Rocky Linux

WEB server = apache 2.6.4

WAS = PHP

DB = mysql or mariaDB 유추 가능

 

Injection Vector

WEB 서버에 공격 코드를 주입할 수 있는 위치

공격자가 악의적인 목적을 가진 코드를 입력했을 때 처리가 가능한 위치

ID / PW 입력칸이 Injection Vector이다 (192.168.50.50에서는~)

 

WEB Vulnerability Scan

Tools에 내장된 공격코드로 알려진 취약점 확인

192.168.50.50을 클릭하고 Scan을 클릭
이러한 취약점이 있다고 알려준다 .

(하지만 공격자도 같은 프로그램을 사용해서 돌리면 취약점을 알아낼 수 있다)


데이터 검증

● 서버에 전달되는 입력값의 검증을 위해 Client 또는 Server에서 실행되는 데이터 확인 과정

● 종류 = Client Side Validation / Server Side Validation

 

Client Side Validation

● Client의 입력을 Client 측(WEB Browser)에서 검증 후 Server에 전달한다

● Browser에서 제공되는 기능, HTML, Javascript 등을 이용하여 검증 후 검증된 데이터를 서버로 전송함

● 장점 : 서버의 부하가 없다

● 단점 : 검증 결과가 쉽게 조작될 수 있음 (Bypassing Client Side Validation)

 

Server Side Validation

● Client의 입력을 Server측에서 검증 후에 받아들임

● SSS(ASP,PHP,JSP 등)로 검증 후 또는 변환 후 검증된 데이터를 서버로 전송함

● 장점 : 검증된 데이터의 신뢰도가 높음

● 단점 : Server의 부하가 높아짐, 필터링 우회가 가능한 경우 조작된 데이터를 받아들일 수 있음

 

Bypassing Client Side Validation

● Client Side Validation 우회

● Client 측에서 검증 코드가 실행되므로 공격자에 의해 조작될 수 있음

 

① Source Code 직접 변조

회원가입 페이지의 ID 입력칸 설정임
maxlength를 지웠음
바로 영향을 받아서 12글자 이상으로 입력이 가능해져버림

※Client측에서 변경한 것이라 Server에 데이터가 전달됐을 때는 어떻게 될 지 모름

검증하는 코드에 의해서 막혀버림 (웹 브라우저가 경고창을 띄움)
등록 버튼을 누르면 ck가 실행된다.
ck에 적혀있던 항목들을 다 지우고 submit만 남기고 회원가입 실행
회원가입 실패함

자바 스크립트를 지워도 이미 적용되어 있는 상태여서 지워도 의미가 없다

화면을 불러올 때 이미 웹 브라우저가 스크립트를 실행해서 코드를 지우는 것은 의미X

 

html은 코드를 변조하면 바로 적용이 된다.

html으로 검증하는 코드를 변조하고 적용하면 우회가 가능하다.

 

하지만 자바 스크립트는 코드를 변조해도 적용이 되지 않는다.

자바스크립트로 검증하는 코드를 변조하고 적용해도 우회가 불가능하다.

 

기존 설정
type이 button이였는데 submit으로 제출하고 ck함수를 지웠음

자바스크립트를 실행하도록 설정되어있었지만 실행하지 못하도록 우회시켰음

회원가입이 성공되었고 DB에 정상 등록되어서 로그인도 가능함

 

② Proxy Tool을 이용한 우회

Client → Proxy → Server (검증이 끝난 데이터를 조작)

 

Trap에서 Trap request를 활성화한 다음에 회원가입 페이지에서 등록을 누르면 Paros에서 데이터를 낚아챈다. 그 데이터를 수정한 화면이다.

여기서도 Trap request를 체크해제하고 Continue를 눌러야 된다

continue를 클릭하면 Server로 전송이 되었기에 회원가입이 완료되었음
DB에 가입정보가 남아있음

 

 

(검증을 삭제한 방법 ↓)

java script를 지워버림
Trap response를 클릭하고 ck함수를 제거했고 자바스크립트를 제거하고 button을 submit으로 변경시키고 회원가입 진행

위 사진처럼 수정하고 Trap response 체크 해제하고 Continue 클릭해야 된다

콘솔창에서 열어도 내가 수정한 코드들로 적용이 되어있다
DB에도 회원가입한 데이터가 저장되어있다

 

보안 대책

검증이 필요한 데이터를 서버 측에서 검증 수행 (Server Side Validation Check)

 

register.php

<!doctype html>
<html>
        <head> 
                <title>회원 가입</title>        
                <link rel="stylesheet" href="../style_contents.css" type="text/css">

                <!-- <script>
                    function ck(){
                        var en = /[^(a-zA-Z0-9)]/;
                        
                        if(en.test(document.mform.user_id.value)){
                            alert("ID는 영문/숫자만 가능합니다.");
                            mform.user_id.focus();
                            return false;
                        }
                        if(document.mform.user_id.value == "" || document.mform.user_id.value.length < 4 || document.mform.user_id.value.length > 12){
                            alert("ID를 다시 입력하세요.");
                            mform.user_id.focus();
                            return false;
                        }
                        if(document.mform.name.value == ""){
                            alert("이름을 입력하세요.");
                            mform.name.focus();
                            return false;
                        }
                        if(document.mform.user_pw1.value == "" || document.mform.user_pw1.value.length < 6 || document.mform.user_pw1.value.length > 20){
                            alert("비밀번호를 다시 입력하세요.");
                            mform.user_pw1.focus();
                            return false;
                        }
                        if(document.mform.user_pw1.value != document.mform.user_pw2.value){
                            alert("비밀번호가 일치하지 않습니다.");
                            mform.user_pw2.focus();
                            return false;
                        }
                        else{
                            document.mform.submit();
                        }
                    }
                </script> -->
        </head>
        <body>
                        <iframe src="../head.php" id="bodyFrame" name="body" width="100%" frameborder="0"></iframe>
                <div id="register_contents"  class="contents">
                <form name="mform" method="post" action="register_ok.php">
                        <table width="550" cellpadding="3" class="grayColor">
                                <tr>
                                <th colspan="2" style="background-color: #323232" >
                                        <font style="color: white; font-size: 150%;" >회 원 등 록</font>
                                </th>
                                </tr>
                                <tr>
                                        <th width="120px"><font>*ID</font></th>
                                        <td>
                                                <input type="text" name="user_id" size="15" maxlength="12">
                                                &nbsp;&nbsp;&nbsp;<font style="color:red;">4~12(영문/숫자)</font>
                                        </td>
                                </tr>
                                <tr>
                                        <th><font>*이   름</font></th>
                                        <td><input type="text" name="name" size="15" maxlength="10"></td>
                                </tr>
                                <tr>
                                        <th><font>*비밀번호</font></th>
                                        <td>
                                                <input type="password" name="user_pw1" size="20" maxlength="20">
                                                &nbsp;<font style="color:red;">6~20(영문/숫자/특수문자)</font>
                                        </td>
                                </tr>

                                <tr>
                                        <th><font>*비밀번호 확인</font></th>
                                        <td><input type="password" name="user_pw2" size="20" minlength="6" maxlength="20"></td>
                                </tr>
                                <tr>
                                        <th><font>나이</font></th>
                                        <td><input type="number" name="age" size="30" min="0" max="150"></td>
                                </tr>
                                <tr>
                                        <th><font>닉네임</font></th>
                                        <td><input type="text" name="nick" size="30" maxlength="30"></td>
                                </tr>
                                <tr>
                                        <th><font>EMAIL</font></th>
                                        <td><input type="text" name="email" size="30" maxlength="30"></td>
                                </tr>
                        </table>
                        <p>
                                <font size=2>* 는 필수 입력 항목입니다.</font><br/><br/>
                                <input type="submit" value="등록" class="btn_default btn_gray" >
                                <input type="reset" value="삭제" class="btn_default btn_gray" >
                        </p>
                </form>
                </div>

        </body>
</html>

script는 주석처리하고 등록버튼을 submit으로 변경

 

register_ok.php에 검증 코드를 넣어야 함 ↓

    if(strlen($id) == 0 || strlen($id) < 4 || strlen($id) > 12){
        echo "<script>
        alert('ID를 다시 입력하세요.111');
        history.back();
        </script>";
        exit();
    }

    if(strlen($name) == 0){
        echo "<script>
        alert('이름을 다시 입력하세요.');
        history.back();
        </script>";
        exit();
    }

    if(strlen($pw1) == 0 || strlen($pw1) < 6 || strlen($pw1) > 20 || $pw1 != $pw2){
        echo "<script>
        alert('비밀번호를 다시 입력하세요.');
        history.back();
        </script>";
        exit();
    }

홈페이지 F12를 눌러서 확인해도 적용되어있음
ID에 15글자를 적었을 시에 나오는 경고창

개인정보 수정하는 info_change.php에도 검증코드를 넣어줘야 된다.

if(strlen($pw1) != 0 || strlen($pw1) < 6 || strlen($pw1) > 20 || $pw1 != $pw2){
        echo "<script>
        alert('비밀번호가 일치하지 않습니다.');
        history.back();
        </script>";
        exit();
    }

조건대로 이행하지 않으면 나타나는 경고창

'어플리케이션 보안 운영' 카테고리의 다른 글

WEB Session Attack  (0) 2025.01.06
WEB 인증 공격  (0) 2025.01.03
Information Gathering (정보 수집)  (0) 2025.01.02
여러기능의 WEB 만들기  (0) 2024.12.31
WEB  (0) 2024.12.26