import gmpy2
from Crypto.Util.number import long_to_bytes, bytes_to_long, getStrongPrime, inverse
from Crypto.Util.Padding import pad
from flag import m
#m = b"ACSC{<REDACTED>}" # flag!
f = open("chal.py","rb").read() # I'll encrypt myself!
print("len:",len(f))
p = getStrongPrime(1024)
q = getStrongPrime(1024)
n = p * q
e = 0x10001
print("n =",n)
print("e =",e)
print("# flag length:",len(m))
m = pad(m, 255)
m = bytes_to_long(m)
assert m < n
stream = pow(m,e,n)
cipher = b""
for a in range(0,len(f),256):
q = f[a:a+256]
if len(q) < 256:q = pad(q, 256)
q = bytes_to_long(q)
c = stream ^ q
cipher += long_to_bytes(c,256)
e = gmpy2.next_prime(e)
stream = pow(m,e,n)
open("chal.enc","wb").write(cipher)
"from flag import m" 못 보고 Q&A 했다가 운영진에게 코드를 읽으면 됩니다라는 어디 숨고 싶은 답변을 들었다 ㅠ.ㅠ
로직은 귀찮아 보이지만 핵심은 같은 modulus에 대해 다른 e가 쓰이고 있다는 점이다.
common modulus attack이라고 하는 이 공격법의 핵심은 아래와 같다.
$$ M^k \bmod N * M^l \bmod N = M^{k+l} \bmod N $$
M < N 이기 때문에 k+l 만 1로 만들면 되는 것이다.
a,b 모두 0이 아니므로 베주 항등식을 적용하면 아래와 같이 $ e_1x + e2y = 1 $ 인 값을 찾으면 된다.
1. 회원가입 ( 불친절하게 회원가입 버튼이 없다,,, ) 2. 로그인 3. "admin이 아닙니다" 하고 다시 로그인 페이지
...
3번에서 막혔었다. 그래서 삽질하면서 여러가지 가능성을 뒀었다. 1. 굳이 함수로 짜져있는 redirect 2. php loose comparision 취약점 3. 배열을 인자로 넘겨서 acc[2]를 만드는 것 4. Bruteforce
1번이 그 해답이였다. redirect 메소드 코드는 아래와 같다.
public function redirect($url, $msg=''){
$con = "<script type='text/javascript'>".PHP_EOL;
if ($msg) $con .= "\talert('%s');".PHP_EOL;
$con .= "\tlocation.href = '%s';".PHP_EOL;
$con .= "</script>".PHP_EOL;
header("location: ".$url);
if ($msg) printf($con, $msg, $url);
else printf($con, $url);
}
철저하게 client side에서 동작하는데 언제나 client side에서 이루어지는 것은 의심해보아야 한다.
challenge 메소드를 확인해보면 redirect 후 끝내는게 아닌 친절하게 c2 파라미터로 받은 것도 확인해준다.
function challenge($obj){
if ($obj->is_login()) {
$admin = new Admin();
if (!$admin->is_admin()) $admin->redirect('/api.php?#access denied');
$cmd = $_REQUEST['c2'];
if ($cmd) {
switch($cmd){
case "gu":
echo json_encode($admin->export_users());
break;
case "gd":
echo json_encode($admin->export_db($_REQUEST['db']));
break;
case "gp":
echo json_encode($admin->get_pass());
break;
case "cf":
echo json_encode($admin->compare_flag($_REQUEST['flag']));
break;
}
}
}
}
export_db 메소드를 확인해보면
public function export_db($file){
if ($this->is_pass_correct()) {
$path = dirname(__FILE__).DIRECTORY_SEPARATOR;
$path .= "db".DIRECTORY_SEPARATOR;
$path .= $file;
$data = file_get_contents($path);
$data = explode(',', $data);
$arr = [];
for($i = 0; $i < count($data); $i++){
$arr[] = explode('|', $data[$i]);
}
return $arr;
}else
return "The passcode does not equal with your input.";
}
db 파라미터의 해당하는 파일을 다운로드 받아주는 것을 알 수 있다. api 자체는 /db/*.db 를 다운 받는 것을 의도했지만 /db/../../../../../../flag를 통해 flag를 다운받을 수 있다.