(CTF 출제)DreamHack - Easy Login

2024. 5. 1. 23:16·DreamHack/CTF

 

 

문제화면이다.

ID, PW, OTP를 입력하면 로그인이 되는 방식인 거 같다.

 

 

 

 

 

 

 

 

 

 

 

 

 

하지만 ID 만 입력했는데도 "Hello,guest" 라는 문구가 뜬다.

 

문제 소스코드를 다운받아 확인해보자.

 

 

 

 

 

 

 

 

 

 

 

 

<?php

function generatePassword($length) {
    $characters = '0123456789abcdef';
    $charactersLength = strlen($characters);
    $pw = '';
    for ($i = 0; $i < $length; $i++) {
        $pw .= $characters[random_int(0, $charactersLength - 1)];
    }
    return $pw;
}

function generateOTP() {
    return 'P' . str_pad(strval(random_int(0, 999999)), 6, "0", STR_PAD_LEFT);
}

$admin_pw = generatePassword(32);
$otp = generateOTP();

function login() {
    if (!isset($_POST['cred'])) {
        echo "Please login...";
        return;
    }

    if (!($cred = base64_decode($_POST['cred']))) {
        echo "Cred error";
        return;
    }

    if (!($cred = json_decode($cred, true))) {
        echo "Cred error";
        return;
    }

    if (!(isset($cred['id']) && isset($cred['pw']) && isset($cred['otp']))) {
        echo "Cred error";
        return;
    }

    if ($cred['id'] != 'admin') {
        echo "Hello," . $cred['id'];
        return;
    }
    
    if ($cred['otp'] != $GLOBALS['otp']) {
        echo "OTP fail";
        return;
    }

    if (!strcmp($cred['pw'], $GLOBALS['admin_pw'])) {
        require_once('flag.php');
        echo "Hello, admin! get the flag: " . $flag;
        return;
    }

    echo "Password fail";
    return;
}

?>

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="style.css">
    <title>Easy Login</title>
</head>
<body>
    <div class="login-container">
        <h2>Login as admin to get flag<h2>
        <form action="login.php" method="post">
            <div class="form-group">
                <label for="id">ID</label>
                <input type="text" name="id"></br>
            </div>
            <div class="form-group">
                <label for="pw">PW</label>
                <input type="text" name="pw"></br>
            </div>
            <div class="form-group">
                <label for="otp">OTP</label>
                <input type="text" name="otp"></br>
            </div>
            <button type="submit" class="button">Login</button>
        </form>
        <div class="message">
            <?php login(); ?>
        </div>
    </div>
</body>
</html>

 

첫 번째 page 인 index.php이다.

 

일단 중요한 부분만 보면은...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<?php

function generatePassword($length) {
    $characters = '0123456789abcdef';
    $charactersLength = strlen($characters);
    $pw = '';
    for ($i = 0; $i < $length; $i++) {
        $pw .= $characters[random_int(0, $charactersLength - 1)];
    }
    return $pw;
}

function generateOTP() {
    return 'P' . str_pad(strval(random_int(0, 999999)), 6, "0", STR_PAD_LEFT);
}

$admin_pw = generatePassword(32);
$otp = generateOTP();

function login() {
    if (!isset($_POST['cred'])) {
        echo "Please login...";
        return;
    }

    if (!($cred = base64_decode($_POST['cred']))) {
        echo "Cred error";
        return;
    }

    if (!($cred = json_decode($cred, true))) {
        echo "Cred error";
        return;
    }

    if (!(isset($cred['id']) && isset($cred['pw']) && isset($cred['otp']))) {
        echo "Cred error";
        return;
    }

    if ($cred['id'] != 'admin') {
        echo "Hello," . $cred['id'];
        return;
    }
    
    if ($cred['otp'] != $GLOBALS['otp']) {
        echo "OTP fail";
        return;
    }

    if (!strcmp($cred['pw'], $GLOBALS['admin_pw'])) {
        require_once('flag.php');
        echo "Hello, admin! get the flag: " . $flag;
        return;
    }

    echo "Password fail";
    return;
}

?>

 

php 전체 부분이다.

 

비밀번호를 생성하는 함수, OTP 생성 함수 , 로그인 함수가 담겨있기 때문이다.

 

 

 

 

 

 

 

 

 

function generatePassword($length) {
    $characters = '0123456789abcdef';
    $charactersLength = strlen($characters);
    $pw = '';
    for ($i = 0; $i < $length; $i++) {
        $pw .= $characters[random_int(0, $charactersLength - 1)];
    }
    return $pw;
}

function generateOTP() {
    return 'P' . str_pad(strval(random_int(0, 999999)), 6, "0", STR_PAD_LEFT);
}

$admin_pw = generatePassword(32);
$otp = generateOTP();

 

비밀번호, OTP 생성하는 함수를 보면 구하기 가 까다로울 거 같다.

 

비밀번호를 생성하는 데는 '0~f'까지의 16진법으로 32 자릿수 이기 때문에 너무 오래 걸려서 다른 방법을 이용해야 한다.

 

그렇다면 로그인 함수에 대한 우회를 해야 한다는 것이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

function login() {
    if (!isset($_POST['cred'])) {
        echo "Please login...";
        return;
    }

    if (!($cred = base64_decode($_POST['cred']))) {
        echo "Cred error";
        return;
    }

    if (!($cred = json_decode($cred, true))) {
        echo "Cred error";
        return;
    }

    if (!(isset($cred['id']) && isset($cred['pw']) && isset($cred['otp']))) {
        echo "Cred error";
        return;
    }

    if ($cred['id'] != 'admin') {
        echo "Hello," . $cred['id'];
        return;
    }
    
    if ($cred['otp'] != $GLOBALS['otp']) {
        echo "OTP fail";
        return;
    }

    if (!strcmp($cred['pw'], $GLOBALS['admin_pw'])) {
        require_once('flag.php');
        echo "Hello, admin! get the flag: " . $flag;
        return;
    }

    echo "Password fail";
    return;
}

 

 

로그인 함수 즉 로그인하는 과정을 전부 우회해야 한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

일단 ID에 "admin"을 입력한 결과 "OTP fail" 이 출력되는 모습을 볼 수 있다.

 

그렇다면 로그인 함수에서 어느 부분에서 막힌 거냐...

 

if ($cred['otp'] != $GLOBALS['otp']) {
        echo "OTP fail";
        return;
    }

 

 

이 부분에서 막혀 "OTP fail" 이 출력된 것이다.

 

버프스위트로 좀 더 자세히 들여다보자.

 

 

 

 

 

 

 

 

 

 

id를 "admin"만 넣어 보냈을 때

 

 

 

 

 

cred 가 base64 인코딩 되어 전송되고 있다.

 

base64를 decode 해서 확인해 보면

 

 

 

 

 

 

 

 

JSON 형식으로 인코딩 된 (POST) 데이터를 볼 수 있다.

 

왜냐하면

 

 

 

 

 

 

 

로그인을 시도할 때 login.php로 들어가게 된다 문제파일을 보면 login.php 이 있다.

 

확인해 보면

 

 

 

<form id="redir" action="index.php" method="post">
    <?php
    $a = array();
    foreach ($_POST as $k => $v) {
        $a[$k] = $v;
    }

    $j = json_encode($a);
    echo '<input type="hidden" name="cred" value="' . base64_encode($j) . '">';
    ?>
</form>

<script type="text/javascript">
    document.getElementById('redir').submit();
</script>

 

 

POST로 전송된 데이터를 배열로 복사하고 , 그 배열을 JSON 형식으로 인코딩한 후에 base64로 인코딩한다.

 

그래서 id, pw, otp 가 배열 형식으로 전달되는 것이다.

 

그럼 다 알았으니 이제 문제를 풀어 보자.

 

 

 

 

 

 

 

 

 

 

if ($cred['otp'] != $GLOBALS['otp']) {
        echo "OTP fail";
        return;
    }

 

 

먼저 아까 보여준 otp를 확인하는 과정이다.

 

여기서 php 비교에 대한 지식이 필요하다.

 

php에서는 "==" , "===" 비교가 있는데

 

"==" (equal) 은 느슨한 비교이고 이것은 "!=" 도 마찬가지이다.

 

"===" (identical) 은 엄격한 비교이다.

 

둘이 차이점은 느슨한 비교는 값만 비교하는 것이고 엄격한 비교는 값과 형식 모두 비교를 한다는 점이다.

 

그래서 연산자를 사용할 때는 데이터 형식이 다르더라도 내용이 같으면 true로 판단한다는 점이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

그렇다면 이 배열에서 우리가 OTP에 대해서 간단한 수정만 한다면 OTP를 우회한다는 것이다.

 

그럼 이제 pw에 대해서 우회해보자.

 

 

 

 

 

 

 

 

 

 

 

 

 

 if (!strcmp($cred['pw'], $GLOBALS['admin_pw'])) {
        require_once('flag.php');
        echo "Hello, admin! get the flag: " . $flag;
        return;
    }

 

 

OTP를 정상적으로 우회했다고 했을 때 pw를 보면 php의 내장 함수인 'strcmp()'을 사용 중인 것을 확인할 수 있다.

 

'strcmp()'는 두 개의 문자열을 비교하여 같다면 0을 반환한다.

 

그래서  '! strcmp()'을 사용하여  두 비밀번호가 일치하는지를 확인하는 거 같다.

 

여기서 'strcmp()'에 대한 취약점은 배열에 들어가는 경우 NULL을 반환한다.

 

PHP에서는 NULL을 문자열과 비교할 때는 빈 문자열과 같은 방식으로 처리된다.

 

따라서 NULL과 빈 문자열을 비교하면 True을 반환한다.

 

 

 

 

 

 

 

그렇다면 pw, otp에 알맞은 값을 base64로 인코딩 시켜 Forward 한다면 플래그가 출력되는 것이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'DreamHack > CTF' 카테고리의 다른 글

(CTF 출제)DreamHack - funjs  (1) 2024.05.03
(CTF 출제)DreamHack - Flying Chars  (0) 2024.05.02
(CTF 출제)DreamHack - Mango  (0) 2024.04.30
(CTF 출제)DreamHack - Carve Party (수정)  (0) 2024.04.05
(CTF 출제)DreamHack - PHPreg (수정)  (0) 2024.04.05
'DreamHack/CTF' 카테고리의 다른 글
  • (CTF 출제)DreamHack - funjs
  • (CTF 출제)DreamHack - Flying Chars
  • (CTF 출제)DreamHack - Mango
  • (CTF 출제)DreamHack - Carve Party (수정)
G_OM
G_OM
최대한 설명 열심히 하려고 합니다. 궁금한 거 있으면 언제든지 물어보셔도 좋습니다.
  • G_OM
    끄적끄적
    G_OM
  • 전체
    오늘
    어제
    • 분류 전체보기 (141) N
      • 모의해킹 (9) N
      • Wargame (69)
        • Linux_bandit (33)
        • Webhacking.kr (36)
      • DreamHack (52)
        • WEB (14)
        • Reverising (9)
        • System (0)
        • CTF (22)
      • Android_security (2) N
        • Drozer_Android (4)
        • Frida_Android (0)
      • 정보보안기사 (2)
      • IT? (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • DreamHack 에 대한 문제들...
  • 인기 글

  • 태그

    정보보안기사
    Android
    bandit
    CTF
    리눅스
    Cookie
    sql injection
    bandit17
    insecurebankv2
    bandit30
    php
    bandit20
    cookies
    php wrapper
    webhacking
    Dreamhack
    webhacking.kr
    안드로이드
    난독화
    url encode
    워게임
    Linux wargame
    wargame
    drozer
    리눅스 워게임
    overthewire
    Linux
    union
    bandit18
    lfi
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
G_OM
(CTF 출제)DreamHack - Easy Login
상단으로

티스토리툴바