https://drive.google.com/file/d/1dRCuDpfTPNPx8wKhuXh0GSCh2w6SiBo_/view?usp=sharing
<?php
include "../../config.php";
if($_GET['view-source'] == 1) view_source();
?><html>
<head>
<title>Challenge 4</title>
<style type="text/css">
body { background:black; color:white; font-size:9pt; }
table { color:white; font-size:10pt; }
</style>
</head>
<body><br><br>
<center>
<?php
sleep(1); // anti brute force
if((isset($_SESSION['chall4'])) && ($_POST['key'] == $_SESSION['chall4'])) solve(4);
$hash = rand(10000000,99999999)."salt_for_you";
$_SESSION['chall4'] = $hash;
for($i=0;$i<500;$i++) $hash = sha1($hash);
?><br>
<form method=post>
<table border=0 align=center cellpadding=10>
<tr><td colspan=3 style=background:silver;color:green;><b><?=$hash?></b></td></tr>
<tr align=center><td>Password</td><td><input name=key type=text size=30></td><td><input type=submit></td></tr>
</table>
</form>
<a href=?view-source=1>[view-source]</a>
</center>
</body>
</html>
분석
위의 소스 코드 중 아래의 코드 부분이 중요한 부분이다.
$hash = rand(10000000,99999999)."salt_for_you";
$_SESSION['chall4'] = $hash;
for($i=0;$i<500;$i++) $hash = sha1($hash);
10000000 ~ 99999999 사이의 값에 "slat_for_you" 문자열을 이어붙힌 뒤 해당 값을 500번 sha1() 함수로 해시화 한다.
그리고 해시화된 값은 화면에 출력된 값이다.
해시 함수는 임의의 길이를 갖는 메시지를 입력받아 고정된 길이의 해시값을 출력하는 함수이다.
암호 알고리즘에는 키가 사용되지만, 해시 함수는 키를 사용하지 않으므로 같은 입력에 대해서는 항상 같은 출력이 나오게 되는데, sha1() 과 같은 해시 함수의 특징 3가지는 아래와 같다.
1. 어떠한 입력값에도 항상 고정된 길이의 해시값이 출력된다.
2. 입력값에서 어느 한 부분이 아주 살짝만 달라져도 전혀 다른 값이 나오게 된다.
3. 단방향 알고리즘 함수라 복호화가 불가능하다.
풀이
위의 해시 함수의 특징 중 복호화가 불가능 하다는 특징으로 인해 복호화 문제는 아니다.
그저 코드를 짜서 10000000 ~ 99999999 사이에서 랜덤으로 뽑은 값에 "salt_for_you" 문자열을 합하여 sha1() 함수를 500번 돌린 값이 문제 화면에 출력된 해시값과 동일하면 이 값에 사용된 10000000 ~ 99999999 사이의 값에 "salt_for_you" 문자열을 합한 값이 답일 것이다.
하지만 10000000 ~ 99999999 사이에서 랜덤으로 뽑아 해시화 한 값이 문제 화면에서 보이는 해시 값과 같은지 비교하는 것은 시간적, 자원적 비효율적이므로
rainbow table을 만들고, 이 rainbow table에서 문제 화면에서 보이는 해시 값의 앞 8자리를 검색해 해당 해시값에 사용된 10000000 ~ 99999999 값을 알아낸다.
(rainbow table은 해시 함수를 이용해 만들어 낼 수 있는 해시 값들을 대량으로 저장한 표다.)
코드는 아래와 같다.
import hashlib
from multiprocessing import Process
def work(id, start, end):
with open("rainbow_table" + str(id) + ".txt", 'w') as f:
for i in range(start, end):
string = str(i) + "salt_for_you"
for j in range(0, 500):
string = hashlib.sha1(string.encode('utf-8')).hexdigest()
f.write(string[0:8] + "[" + str(i) + "]" + '\n')
if __name__ == "__main__":
th1 = Process(target=work, args=(1, 10000000, 20000001))
th2 = Process(target=work, args=(2, 20000001, 30000001))
th3 = Process(target=work, args=(3, 30000001, 40000001))
th4 = Process(target=work, args=(4, 40000001, 50000001))
th5 = Process(target=work, args=(5, 50000001, 60000001))
th6 = Process(target=work, args=(6, 60000001, 70000001))
th7 = Process(target=work, args=(7, 70000001, 80000001))
th8 = Process(target=work, args=(8, 80000001, 90000001))
th9 = Process(target=work, args=(9, 90000001, 100000000))
th1.start()
th2.start()
th3.start()
th4.start()
th5.start()
th6.start()
th7.start()
th8.start()
th9.start()
th1.join()
th2.join()
th3.join()
th4.join()
th5.join()
th6.join()
th7.join()
th8.join()
th9.join()
파이썬에서는 GIL 정책으로 인해 멀티 스레딩을 할 수 없다.
Threading 모듈을 이용하더라도 싱글 스레드와 멀티 스레드의 작업 수행 속도는 같다.
그러므로 멀티 프로세싱을 이용해 멀티 작업을 한다.
위의 코드는 10000000 ~ 99999999의 값을 한 번에 작업하면 시간도 오래 걸리고 비효율적이므로 10000000 단위로 나누어 총 9개의 프로세스가 돌아가도록 하였다.
위의 코드를 실행하면 9개의 txt 파일이 생기는데 각 파일에서 해시 값의 앞 8자리 c580a8a1를 검색한다.
c580a8a1 값은 rainbow_table6.txt 파일에 있었다.
해당 해시값에 사용된 10000000 ~ 99999999 사이의 값은 62684899이다.
62684899salt_for_you가 비번이다.
Flag
62684899salt_for_you
INFO
rainbow table을 만드는 데에는 시간이 많이 필요하다.
이러한 단점으로 인해 레인보우 테이블을 작은 크기로 줄이는데 사용하기 위해 나온 것이 R 함수이다.
(일정한 패턴이나 유사한 것들을 이용해 모든 값이 아닌 특정한 값을 저장하는 방법이다.)
이번 문제를 풀기 위해서는 일단 위의 소스코드로 rainbow table을 만들고 문제를 새로 고침해가며 앞 8자리를 rainbow_table.txt 파일에서 검색해서 알아내야 한다.
시간이 꽤 필요로한다.
참고 URL
https://monkey3199.github.io/develop/python/2018/12/04/python-pararrel.html
'전쟁 > Webhacking.kr' 카테고리의 다른 글
[Webhacking.kr] old-15 (0) | 2022.07.13 |
---|---|
[Webhacking.kr] old-26 (0) | 2022.07.13 |
[webhacking.kr] old-19 (0) | 2022.05.06 |
[webhacking.kr] old-6 (0) | 2022.05.06 |
[webhacing.kr] old-12 (0) | 2022.05.04 |