난 역시 정리하면서 문제푸는게 편하다…
<?php
$a = array();
foreach ($_POST as $k => $v) {
$a[$k] = $v;
}
$j = json_encode($a);
echo '<input type="hidden" name="cred" value="' . base64_encode($j) . '">';
?>
먼저 여기서 로그인, 패스워드, otp 값을 입력받고 검증을 한다.
일단 입력값이 들어오면 json encoding → base64 encoding을 해서 저장하는 것을 확인할 수 있다.
<?php
function generatePassword($length) {
$characters = '0123456789abcdef';
$charactersLength = strlen($characters); //16?
$pw = '';
for ($i = 0; $i < $length; $i++) { //전달받은 param 값 = length
$pw .= $characters[random_int(0, $charactersLength - 1)]; #0 ~ 15까지 randomint를 생성해서 하나씩 붙이는건
}
return $pw;
}
function generateOTP() {
return 'P' . str_pad(strval(random_int(0, 999999)), 6, "0", STR_PAD_LEFT);
}
$admin_pw = generatePassword(32);
$otp = generateOTP();
function login() {
if (!isset($_POST['cred'])) {
echo "Please login...";
return;
} // default 설
if (!($cred = base64_decode($_POST['cred']))) {
echo "Cred error";
return;
}
if (!($cred = json_decode($cred, true))) {
echo "Cred error";
return;
}
if (!(isset($cred['id']) && isset($cred['pw']) && isset($cred['otp']))) {
echo "Cred error";
return;
}
if ($cred['id'] != 'admin') {
echo "Hello," . $cred['id'];
return;
}
if ($cred['otp'] != $GLOBALS['otp']) {
echo "OTP fail";
return;
} // 느슨한 비교
if (!strcmp($cred['pw'], $GLOBALS['admin_pw'])) {
require_once('flag.php');
echo "Hello, admin! get the flag: " . $flag;
return;
}
echo "Password fail";
return;
}
?>
입력으로 JSON 문자열을 받아 JSON 문자열의 구문을 분석하고, 리턴값으로 그에 대한 JavaScript 값이나 객체를 생성
JSON.parse() - JavaScript | MDN
return 'P' . str_pad(strval(random_int(0, 999999)), 6, "0", STR_PAD_LEFT);
문자열을 지정한 길이가 되도록 다른 문자열로 채우는 함수
OTP의 값은 P로 시작하는 7자리
취약점이 나타나는 함수 부분
만약에 배열과 문자를 비교할려고 할 때→ strcmp() 함수는 null을 반환한다.
입력 값으로 배열을 넣을 경우 → strcmp() 함수는 리턴값으로 0을 출력함…ㅋ
훌륭한 예시가 있다.
이러한 문제가 발생하는 건 php 5.3 ver이라고 한다.
php 5.3 ver에서, null값은 0으로 취급된다.