php
PHP - 패스워드 스니핑
리플레이 공격방식은 프레젠테이션 공격이라 부르는데, 정상적인 사용자가 인증권한을 취득하기 위해 전송한 데이터를 재전송하는 모든 종류의 공격을 말합니다.
리플레이 공격과 마찬가지로 패스워드 스니핑 공격을 차단하려는 노력은 많지만 완전하게 차단할 수 있는 방법은 없습니다.
그렇기 때문에 인증과정 중 폼의 여러 가지 속성을 설치한다 하더라도 공격자에게는 그다지 중요하지 않게 생각할 수 있으며 그래서인지 요즘은 HTTP요청내용과 HTTP응답내용이 노출되지 않게 보호하려고 SSL전송방식을 많이 이용하는 편입니다.
<form action=https://mydomain.com/login.php method="POST">
myid: <input type="text" name="username">
mypass: <input type="password" name="userpass">
</form>
개인이 호스팅을 임대받는 경우라면 SSL기능을 자유롭게 사용할 수 없으므로 get 전송방식보다는 인증노출이 적은 post 방식을 사용할 것을 권장하며, 비밀번호는 값을 MD5로 암호화하되 복잡한 암호를 사용하도록 사용자에게 유도하거나 개발자가 임의의 문자를 섞여 한번 더 암호화시켜 주어야 합니다.
다음으로 인증요청 무차별 공격 스크립트를 작성할 수 있습니다.
<form action="http://mydomain.com/login.php" method="POST"><br />
myid: <input type="text" name="username"><br />
mypass:<input type="password" name="userpass"><br />
</form>
<?php
$host = "mydomain.com";
$username = "habony";
$userpass = "1111";
$data = "username=$username&userpass=$userpass";
$len = strlen($data);
// 헤더 구분은 \r\n로 해주어야 합니다.
$request = '';
$request .= "POST /login.php HTTP/1.1\r\n";
$request .= "Host: ${host}\r\n";
$request .= "Content-Type: application/x-www-form-urlencoded\r\n";
$request .= "Content-Length: ${len}\r\n";
$request .= "Connection: close\r\n";
// 본문시작은 \r\n\r\n로 헤더와 바디로 구분합니다.
$request .= "\r\n";
$request .= "$data";
// HTTP요청은 기본 80포트입니다.
if($fp = fsockopen($host, 80)){
// mydomain.com 에 HTTP요청하고, HTTP응답을 받습니다.
fputs($fp, $request);
$response = '';
while(!feof($fp)){
// 1줄씩 응답을 읽어 옵니다.
$response .= fgets($fp, 1024);
}
fclose($fp);
}
echo "$response<br />\n";
?>
이와 같은 작업으로 공격자는 인증시도를 하려할 것인데, $response의 결과에 따라 패스워드 취득 실패시 다른 패스워드 재시도 루틴을 요청할 수 있을 것입니다.
만약 사용자가 복잡한 암호를 구성하였다면, 공격자의 성공확률을 낮출 수 있습니다. 사용자 암호보호를 위해 암호화하는 것은 필요한 것이므로 무차별 공격을 어렵게 만들거나 성공확률을 낮추기 위해 30초정도 지연시킬 수 있습니다.
<?php
$uniqid = uniqid(rand());
// 아이디에 위험한 문자열이 있을 경우 ...
$inputid = mysql_real_escape_string($_POST['inputid']);
// 사용자가 단순한 암호를 선택하였다면,
// 개발자가 임의 문자를 붙여 한번 더 암호화시켜줍니다.
// md5 함수는 16진수 32 문자로 반환합니다.
// 16진수 32문자 = md5(문자열, [raw_output]);
// raw_output는 PHP 5.0부터 사용 가능하며,
// true로 설정하면 길이 16의 바이너리 형식으로 반환합니다.
// md5는 문자열 암호화이고, md5_file는 파일 암호화로 동일하게 사용할 수
있습니다.
// echo md5("myid"); // 결과:cdce51bb5b16a770fbe0dd78e6d8a5bb
// echo md5("myid", true); // 결과: 誥Q?쬹鎬?燕?
$inputpass = md5($uniqid . "_habony_" . md5("habony_" .
$_POST['inputpass'], true));
$data = array();
$sql = array();
$now = time();
$login_conn = $now - 30;
if($sql = mysql_query("select logintime, inputpass from $db where
inputid='${inputid}'")){
if(mysql_num_rows($sql)){
$row = mysql_fetch_assoc($sql);
if($row['logintime'] > $login_conn){
exit("로그인 실패하여 30초가 지나야 로그인 가능합니다.");
} elseif($row['inputpass'] === $inputpass){
echo "로그인 인증되었습니다.";
} else {
// 로그인 실패시 처리 부분...
mysql_query("update $db set logintime = '$now' where
inputid = '$inputid' ");
}
} else {
exit("해당하는 아이디가 없습니다.");
}
}
?>
파일업로드된 파일접근을 막기 위해 이것 역시 암호화하여 저장할 필요가 있습니다.
<?php
$uniqid = uniqid(rand());
// 16진수 32문자 = md5_file(파일명, [raw_output]);
// raw_output는 PHP 5.0부터 사용 가능하며,
// true로 설정하면 길이 16의 바이너리 형식으로 반환합니다.
// md5_file함수는 파일이 실제 존재해야 암호화됩니다.
// 실패하면 php오류코드를 표시합니다.
// md5_file은 다음과 같은 조건입니다.
// if(file_exists($filename)){
// $md5filename = md5($uniqid . "_habony_". md5($filename, true));
// }
$filename = base64_encode($_FILES['userfile']['name']);
$md5filename = md5($uniqid . "_habony_". md5_file($filename, true));
if($_FILES['userfile']['error'] === UPLOAD_ERR_OK) {
if($_FILES['userfile']['size'] <= 0){
echo "파일 업로드에 실패하였습니다.";
} else {
// HTTP post로 전송된 것인지 체크합니다.
if(!is_uploaded_file($_FILES['userfile']['tmp_name'])) {
echo "HTTP로 전송된 파일이 아닙니다.";
} else {
// move_uploaded_file은 임시 저장되어 있는 파일을 ./uploads 디렉토리로 이동합니다.
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $md5filename)) {
echo "성공적으로 업로드 되었습니다.\n";
} else {
echo "파일 업로드 실패입니다.\n";
}
mysql_query("insert into $db values ('','$filename');
}
}
} else {
echo file_errmsg($_FILES['userfile']['error']);
}
?>
0 댓글