Session & HTTP Session hijacking
Session
세션 데이터는 세션 데이터 저장소에 보관된다. SID라는 고유 식별자로 세션 데이터 저장소에서 특정 레코드를 식별한다. 세션 저장소 경로는 php.ini의 session.save\_path
에 지정되며 session\_save_path()
로 얻거나 설정할 수 있다. 이를 이용해 세션 저장소에 직접 접근해서 다른 세션 데이터를 얻어올 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$path = ini\_get('session.save\_path');
$handle = dir($path);
while ($filename = $handle->read()) {
if (substr($filename, 0, 5) == 'sess\_') {
$data = file\_get\_contents("$path/$filename");
if (!empty($data)) {
session\_decode($data);
$session = $\_SESSION;
$\_SESSION = array();
echo "Session [" . substr($filename, 5) . "]\n";
print\_r($session);
echo "\n--\n\n";
}
}
}
PHP의 PHPSESSID 처리
session_start()
를 호출하면 PHP는 현재 요청의 cookiequery string에 PHPSESSID
가 있는지 확인한다.- SID가 있는 경우 : 저장소에서 해당 세션의 데이터를 읽어들이고
$_SESSION
에 저장한다. SID가 없는 경우 : SID를 생성하고 세션 데이터 저장소에 새로운 레코드를 생성한다. - 쿠키를 설정하고 헤더를 캐싱한다.
HTTP Session hijacking ( L7 )
HTTP 자체는 stateless protocol이라, SID를 이용해 세션을 식별하고, SID는 결국 쿠키이므로, 쿠키를 훔쳐내면 다른 사용자의 세션을 가져올 수 있다.
유효한 SID를 얻어내기 위한 방법으로는 세 가지가 있다.
- 예측
- 캡쳐
- 고정
예측
보통 SID는 충분히 랜덤하게 생성되기 때문에 이 방법으로는 어렵다.
캡쳐
monitor 모드로 패킷 스니핑해서 가져오는 방법을 사용할 수 있다.
고정
PHP는 요청에 포함된 SID가 있으면 새로운 SID를 생성하지 않고 그 SID에 연결된 세션 저장소를 사용한다. 이를 이용해 victim이 공격자가 미리 지정해 놓은 SID를 사용하도록 하는 방법이다.
1
<a href="http://example.org/index.php?PHPSESSID=1234">example.org</a>
query string에 PHPSESSID
를 지정하고 redirect시키는 방법도 있다.
1
2
<meta http-equiv="refresh" content="0;
url=http://example.org/index.php?PHPSESSID=1234" />
1
<script>location.href='http://example.org/index.php?PHPSESSID=1234'</script>
이렇게 해서 만들어진 세션 데이터 저장소 레코드는 PHPSESSID=1234
를 지정하면 접근할 수 있어 공격자가 세션 데이터를 얻어낼 수 있다.
세션 고정 공격을 예방하기 위해서는 SID를 발급할 때 flag를 설정해두고, SID를 전송받았을 때 flag가 설정되어 있는지를 체크해 실제로 발급한 SID인지를 확인하는 방법이 있다.
1
2
3
4
5
if (!isset($\_SESSION['initiated'])){
session\_regenerate\_id();
$\_SESSION['initiated'] = TRUE;
}
그러나 이 방법은 공격자가 해당 사이트를 방문해서 SID를 받은 다음, 이 SID를 고정 공격에 사용하면 해당 사이트에서 정상적으로 발급받은 SID가 맞기는 하니까 우회할 수 있다.
그래서 다른 방법을 사용해야 한다. 생각해보면 세션 고정 공격은 공격자가 정상적으로 얻을 수 있는 권한보다 높은 권한을 가진 SID를 획득하기 위해 수행하는 공격이므로, 로그인 하는 등 권한이 변경될 때 마다 SID를 재생성한다면 무력화할 수 있다.
1
2
3
4
5
6
$\_SESSION['logged\_in'] = FALSE;
if (check\_login()){
session\_regenerate\_id();
$\_SESSION['logged\_in'] = TRUE;
}
단, 모든 페이지에서 SID를 재생성하는 것은 비효율적이며 그렇게 한다고 더 안전한 것도 아니다.
Note ) 뒤로가기 했는데 갑자기 로그인이 풀린다던가 하는 것은 SID가 변경되었고, 뒤로가기 하면서 변경되기 전의 SID를 가리키게 돼서 그렇다.
is_hijacked?
User-Agent
의 일관성을 검사하는 건 별로지만, 심층 방어의 관점에서 보면 안하는 것 보다는 낫다.
1
2
3
4
5
6
7
if (isset($\_SESSION['HTTP\_USER\_AGENT']){
if ($\_SESSION['HTTP\_USER\_AGENT'] != md5($\_SERVER['HTTP\_USER\_AGENT'])){
// password authentication or exit
}
} else {
$\_SESSION['HTTP\_USER\_AGENT'] = md5($\_SERVER['HTTP\_USER\_AGENT']);
}
그러나 일반적으로 SID는 쿠키에 넣어서 전송되기 때문에 SID를 알고 있다면 모든 HTTP header 정보를 알고 있을 가능성이 높고, 이 경우 User-Agent
를 비롯한 모든 값을 복사해 사용할 수 있다.
따라서 검사에 사용하는 정보들을 salt와 함께 해싱한 다음 이를 토큰으로 사용해서 페이지의 모든 내부 링크가 query string에 토큰을 포함하도록 구성하는 방법으로 보완할 수 있다.
1
2
3
4
5
6
7
8
9
<?php
$url['token'] = rawurlencode($token);
$html['token'] = htmlentities($url['token'], ENT\_QUOTES, 'UTF-8');
?>
<form action="index.php?token=<?= $html['token'];?>">
...
세션 하이재킹을 막기 위해서는
- SID를 비공개하거나
- 세션이 hijacking되었는지를 식별할 수 있는 방법을 사용하거나 둘 중 한 가지를 만족해야 한다.
1.
SID 비공개는 SSL로 처리하면 된다. 근데 암복호화를 사용하는건 어쨌든 오버헤드를 동반하는 일이기도 하고, 하나의 페이지라도 HTTP로 처리한다면 거기서 SID가 노출되니까 모든 페이지를 SSL로 처리해야만 한다. 대체로 이 방법을 사용한다. 구글이나 페이스북이나…
그러나 SSL을 이용하는 경우에도 ClientHello
에 이전 SID를 적어 보내면 이전 세션 연결이 재개되기 때문에 이를 이용해 hijacking 할 수 있다. 고 하는데 직접 해보지는 않았다.
2.
src IP를 사용해 검증하는 방법이 괜찮기는 한데, 이건 각 웹사이트들이 IP보안이라는 항목으로 제공하고 있는 듯. 그리고 모바일에서는 이동하면서 IP가 변경되거나 와이파이를 잡는 경우 매번 다시 로그인해야 하기 때문에 유저가 불편함. 물론 IP도 spoof 할 수 있지만, 서로 다른 원거리 서브넷에 위치한 victim으로 IP spoof한다면 라우팅이 제대로 되지 않아 response가 돌아오지 않을 듯. ( 실험해보지는 않았다. ) PHP 단에서 L3 real IP addr을 확인하는 방법은 $\_SERVER['REMOTE_ADDR']
를 사용하는 것.