리플레이 공격방식은 프레젠테이션 공격이라 부르는데, 정상적인 사용자가 인증권한을 취득하기 위해 전송한 데이터를 재전송하는 모든 종류의 공격을 말합니다. 리플레이 공격과 마찬가지로 패스워드 스니핑 공격을 차단하려는 노력은 많지만 완전 차단방법은 없습니다.

그렇기 때문에 인증과정 중 폼의 여러 가지 속성을 설치한다 하여 공격자에게는 그다지 중요하지 않다 생각되는 잡다한 것으로 보며, 그래서인지 요즘은 HTTP요청내용과 HTTP응답내용이 노출되지 않게 보호하려는 방법으로 SSL전송방식을 많이 애용하는 편입니다. 허나 누가 뭐라해도 패스워드를 보호하는 일이야 말로 가장 중요한 부분임을 누구도 거부하지 않습니다.

이제는 _POST방식도 인증보호에 기대가 없고, 그렇다고 MD5도 믿음이 있어 보이지 않습니다. 왜냐하면 몇 년전에 이미 MD5가 복호화되어 암호가 뚤려 버렸기 때문입니다.

 <form action=https://mydomain.com/login.php method="POST"><br />
 myid:     <input type="text" name="username"><br />
 mypass:<input type="password" name="userpass"><br />
 </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($host80
)){
      
// mydomain.com 에 HTTP요청하고, HTTP응답을 받습니다.
      
fputs($fp$request
);

      
$response ''
;
      while(!
feof($fp
)){
            
// 1줄씩 응답을 읽어 옵니다.
           
$response .= fgets($fp1024
);
      }
      
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?&#52025;鎬?燕?
 
$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($filenametrue
));
 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']);
 }
 ?>


블로그 이미지

하보니

하보니와 함께하는 phP 초보

댓글을 달아 주세요

  • MD5 2011.09.27 22:30  댓글주소  수정/삭제  댓글쓰기

    MD5가 뚫렸다는게 해시에서 원본을 찾아낼수 있게 뚫렸다는게 아닙니다.
    해시는 기본적으로 강한 충돌저항성을 가지는데 임의로 충돌을 만들어 내는데 성공했다고 합니다.
    이를 이용하면 예를들어 가짜 인증서를 만들수 있겠죠...

  • MD5 2011.09.27 22:31  댓글주소  수정/삭제  댓글쓰기

    일단 간단한 암호의 경우에는 브루트포스나 레이보우테이블을 이용해서 뚫는게 가능합니다.
    다만 충분히 강한 SOLT를 붙이면 훨씬 뚫기 힘들어지겠죠...

    • Favicon of https://blog.habonyphp.com BlogIcon 하보니 2011.09.28 18:17 신고  댓글주소  수정/삭제

      단순히 [a-z 0-9]로 이루어진 문자로는 보안이 약하다는 뜻이네요. 중간에 특수문자를 섞어두면 좀 안심이 되지 않을까요?

      조은 정보 감사합니다.