Superkkt Blog

요즘 대부분의 포털 사이트에서 로그인 과정에 Secure HTTP를 지원한다. URL이 https://~~로 시작하는데 이 프로토콜 상에서 전송되는 데이터는 모두 암호화되기 때문에 누군가가 패킷을 중간에서 가로채더라도 사용자가 입력한 암호(일반 텍스트)를 알아낼 수 없다.

하지만 아직도 많은 수의 웹사이트가 Secure HTTP를 지원하지 않는다. 그래서 사용자가 입력한 비밀번호가 일반 텍스트 형태로 네트웍으로 전송된다. 당연히 악의적인 사용자가 패킷을 중간에서 가로채면 비밀번호가 노출되고, 여러 사이트에 동일한 아이디와 비밀번호를 사용하는 사용자가 많기 때문에 다른 웹사이트까지 연쇄적으로 뚫리는 결과를 야기하게 된다.

이 문제에 대한 해결 방법은 여러가지가 있지만 가장 좋은것은 Secure HTTP를 사용하는 것이다. 하지만 이 방법은 웹사이트 관리자가 웹서버의 관리자 권한도 동시에 가지고 있어야 하며, 설치하기 귀찮다는 단점이 있다.

그럼 두번째로 사용 할 수 있는 방법은 뭐가 있을까? 바로 일회용 비밀번호를 사용하는 것이다.

일회용 비밀번호의 원리는 간단하다. 로그인을 시도하려는 클라이언트에게 서버가 일회용 key를 발급하고 클라이언트는 이 key를 사용해서 사용자가 입력한 비밀번호를 암호화한다. 이렇게 암호화된 비밀번호를 네트웍으로 전송하면 공격자가 패킷을 가로채더라도 사용자가 입력한 암호를 알 수가 없다.

1. 클라이언트가 로그인 페이지에 접속
2. 서버는 unique하고 random한 key를 생성해서 클라이언트에 전송
3. 클라이언트는 사용자가 입력한 비밀번호(일반 텍스트)와 서버에서 발급받은 key를 가지고 지정된 연산을 수행해서 암호화된 문자열을 생성하고, 발급 받은 key와 함께 서버로 전송
4. 서버는 데이터베이스에 저장된 실제 비밀번호와 클라이언트가 전송한 key를 가지고 지정된 연산을 수행해서 암호화된 문자열을 생성하고, 클라이언트가 전송한 암호화된 문자열과 비교

위에서 지정된 연산은 MD5나 SHA1 같은 hasing 알고리즘을 사용하면 된다.

그러나 여기서 한가지 중요한 문제가 있다. 사용자가 입력한 비밀번호가 암호화 되어서 공격자가 패킷을 가로채더라도 원래 암호를 알아낼수는 없지만 암호화된 비밀번호를 그대로 사용해서 해당 웹사이트에 로그인을 할 수 있다.

이 문제도 같이 해결하려면 위 로그인 과정 중 4번에서 서버가 클라이언트에서 전송한 key의 유효성을 체크하도록 해야한다. 유효성을 체크하는 가장 안전한 방법은 서버가 발급했던 key를 모두 저장시켜놓고 한번 로그인에 사용되었던 key는 다시 사용 할 수 없도록 하는 것이다.

이 방법이 구현하기 귀찮다면 좀 덜 안전한 방법이지만 서버가 발급하는 key로 UNIX timestamp를 사용하고 위 4번 과정에서 현재 timestamp와 클라인트가 전송한 key를 비교해서 지정된 유효시간을 벗어났으면 로그인을 거부하면 된다. 나는 내 블로그의 로그인 과정을 이 방법을 사용(유효시간은 30초)해서 암호화된 스트링을 사용하도록 변경하였다.

아래 내용은 내 블로그에서 사용 중인 테터툴즈 코드를 수정한 내용 중 일부를 발췌한것이다.

참고로 서버의 DB에 실제 비밀번호는 MD5로 해슁된 내용이 입력되어 있다. 그리고 MD5와 SHA1을 수행하는 자바스크립트 코드(BSD License라서 상용으로 사용해도 무방하다)는 여기서 구할 수 있다.

아래 코드에서는 사용자가 입력한 비밀번호를 암호화 하는데 SHA1 알고리즘을 사용했고, 서버가 발급하는 일회용 key는 UNIX timestamp를 사용했다.

1. 로그인 HTML 페이지

<?
$onetime_passwd_key = time();
?>

<script type="text/javascript" src="/script/md5.js"></script>
<script type="text/javascript" src="/script/sha1.js"></script>
<script type="text/javascript">
function do_submit()
{
  var frm = document.forms[0];

  if (frm.loginid.value == "")
  {
       alert('E-mail을 입력하세요.');
       frm.loginid.focus();
       return;
  }
  if (frm.password.value == "")
  {
       alert('비밀번호를 입력하세요.');
       frm.password.focus();
       return;
  }

  frm.password.value = hex_sha1('<?=$onetime_passwd_key?>' + hex_md5(frm.password.value));
  frm.submit();
}
</script>


2. 로그인 PHP 페이지

define("LOGIN_EXPIRATION_TIME", 30);

function login($loginid,$password,$onetime_passwd_key){
  $loginid=mysql_escape_string($loginid);
  $onetime_passwd_key = mysql_escape_string($onetime_passwd_key);
  if (time() > $onetime_passwd_key + LOGIN_EXPIRATION_TIME)
  {
       echo "<script>alert('로그인 페이지 유효시간(". LOGIN_EXPIRATION_TIME .")이 만료되었습니다.');</script>";
       return false;
  }
  $password = mysql_escape_string($password);
  $secret='SHA1(CONCAT(\''. $onetime_passwd_key .'\', password)) = \''. $password .'\'';

  if($result=mysql_query("SELECT userid, loginid, name FROM {$database['prefix']}Users WHERE loginid = '$loginid' AND $secret")){
       if($session=mysql_fetch_array($result)){
           /* 로그인 성공 */
           return true;
       }
  }
  return false;
}

2006/12/24 14:33 2006/12/24 14:33

trackbacks

trackbacks rss

이 글에는 트랙백을 보낼 수 없습니다

  1. M/D R
    아앗. 저와 비슷한 생각을 하셨군요. 태터 플러그인으로 만들어보세요. 저는 잘 만들려다가 잠시 접어두었습니다.
    • 김기태 2006/12/26 09:30
      M/D
      테터툴즈 공식 사이트에 "플러그인 만들기" 메뉴얼이 있어서 봤는데 미완성이라서 대략 난감이군요. 나중에 시간날때 아무 플러그인이나 분석해봐서 어렵지 않으면 한번 만들어 보겠습니다. ^^

Leave a Comment