본문 바로가기
WEB HACKING/웹 해킹[실습]

웹 시큐어 코딩 : 인증 및 인가

by madevth 2022. 3. 5.
반응형

저번 포스팅에서는 파일 업로드 / 다운로드 취약점에 대응하여 웹 시큐어 코딩을 해보았다.

파일 업로드, 다운로드 방어하기 : [모의해킹_실습] - 웹 시큐어 코딩 : 파일 업로드 & 다운로드

이번 포스팅에서는 인증과 인가 취약점을 막아보자!

 

인증 및 인가 취약점이 일어나는 대표적인 곳은 url을 통해 페이지에 직접 접근이 가능한 경우이다. 필자가 개발한 웹 사이트에서는 비회원이 read.php?id=n로 바로 접근하거나, 회원이 delete.php?id=n으로 타인의 게시물을 삭제하는 경우를 예로 들 수 있다.

 

시큐어 코딩을 할 때 회원들만 이용할 수 있는 게시판에서는 서버 측 세션 검증을 추가해주었다.

문의 게시판의 경우 비회원도 이용할 수 있기 때문에 비밀번호를 확인하는 페이지를 거쳤는지를 확인하는 기능을 추가해주었다.

 

① 세션 검증

session_start();
if (!isset($_SESSION['id'])){
    echo "<script>alert('잘못된 접근입니다.'); history.back();</script>";
}

세션 아이디가 존재하지 않는다면, 로그인한 사용자가 아니므로 접근을 막아주었다.

 

그런데, 이런 방식으로 접근을 막으면 BurpSuite으로 우회가 가능하다.

요청 및 응답을 확인해보면 위의 사진처럼 스크립트 태그가 있고 바로 그 밑에 html이 보이는 것을 확인할 수 있다. 이 경우 BurpSuite에서 응답 패킷을 잡아서 스크립트를 지우면 다음 코드가 실행되기 때문에 우회가 가능하다.

 

[BurpSuite에서 Response 수정하기]

우선 Proxy → Options에서 Intercept Client Requests에 체크를 해제하고, Intercept Server Responses에 체크해준다.

And, URL, Matches로 규칙을 추가한 후 체크해준다.

 

그 후 Intercept에서 Intercept를 On으로 해준 뒤 read.php?id=30에 들어가면 응답을 잡아준다.

여기서 <script> 부분만 삭제하고 Intercept를 해제해주면, 다음과 같이 게시물을 확인할 수 있다.

 

따라서 클라이언트 측이 아니라 서버 측에서 접근을 막아야 한다.

 

session_start();
if (!isset($_SESSION['id'])){
    header("Location: index.php");
    die();
}

 

스크립트 대신 header를 사용하니 Burp Suite에서 렌더링 되기 전에 redirect 시킬 수 있었다.

 

[스크립트로 접근 막을 때 응답 패킷의 일부]

HTTP/1.1 200 OK
Date: Fri, 04 Mar 2022 14:57:21 GMT
Server: Apache
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 1871
Connection: close
Content-Type: text/html; charset=UTF-8

<script>alert('잘못된 접근입니다.'); history.back();</script><!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="./css/style.css"  rel = "stylesheet"/>
    <title>Read Posting</title>
</head>
<body>
    <div class = "column">
        <div class = "posting">
            <br />
            <div class = "posting_title"></div>
            <div class = "posting_contents"></div>
                        <div class = "btns">
                <button class = "writeBtn" onclick = "location.href = 'board.php'">게시판</button>
                <button class = 'writeBtn' onclick = "location.href = 'update.php?id=30'">수정</button><button class = 'writeBtn' onclick = "location.href = 'board_delete.php?id=30'">삭제</button>            </div>
        </div>
    </div>
    <script
        src="https://kit.fontawesome.com/6478f529f2.js"
        crossorigin="anonymous"
    ></script>
</body>
</html>

 

[redirect로 접근 막을 때 응답 패킷]

HTTP/1.1 302 Found
Date: Fri, 04 Mar 2022 15:00:01 GMT
Server: Apache
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: index.php
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8

깔-끔

 

 

② 특정 페이지를 거쳤는지 체크

문의 게시판은 1. 문의글 클릭, 2. qna_check.php에서 비밀번호 입력받고, 유효성 검사, 3. 비밀번호 일치 시 qna_read.php로 리다이렉트의 세 단계로 이루어져 있다.

여기서 문제는 2번 단계를 건너뛰고 바로 qna_read.php로 들어가면 문의글을 확인할 수 있다는 것이다.

그래서 qna_check.php를 거쳤는지, 안 거쳤는지를 qna_read.php에서 확인하도록 로직을 추가해주었다.

 

[qna_check.php]

if(array_key_exists('pw_check', $_POST)){
    $pw = $_POST['anonypw'];
    $_SESSION['qnapw'] = $pw;
    [DB에서 id에 해당하는 패스워드를 찾아 입력 값과 비교]
}

위와 같이 비밀번호를 입력받았다면, 그 값을 $_SESSION['qnapw']에 저장해준다.

 

[qna_read.php]

$check = 1;
if(isset($_SESSION['id']) && $_SESSION['id'] == "admin") {
    $sql = "SELECT * FROM qna where id = ?";
    $pre_state = $conn->prepare($sql);
    $pre_state->bind_param("s", $id);
    $login_user = $_SESSION['id'];
}
else if(isset($_SESSION['id'])) {
    $sql = "SELECT * FROM qna where id = ? and username = ?";
    $pre_state = $conn->prepare($sql);
    $pre_state->bind_param("ss", $id, $login_user);

    $login_user = $_SESSION['id'];
} else if(isset($_SESSION['qnapw'])){
    $sql = "SELECT * FROM qna where id = ? and password = ?";
    $pre_state = $conn->prepare($sql);
    $pre_state->bind_param("ss", $id, $password);

    $password = $_SESSION['qnapw'];
    $login_user = "";
    unset($_SESSION['qnapw']);
} else{
    $check = 0;
}

qna_read.php에서는 id가 존재하고 admin인 경우, id가 존재하는 경우, qnapw값이 존재하는 경우(비회원이며 비밀번호 확인 페이지를 거친 경우)로 나누어 SQL문을 실행하고, 세 가지 경우 모두 해당되지 않는 경우는 $check를 0으로 바꾸어 접근을 거부했다.

 

이번 포스팅에서는 인증/인가 취약점에 대응한 시큐어 코딩을 해보았다.

반응형

댓글