[ Natas 20 -> Natas 21 Session Hijacking ]

2017. 10. 7. 19:42WebHacking/[OverTheWire]Natas


1> 첫 페이지 화면


2> 페이지 소스코드 ( PHP 코드 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
 
 
<? 
 
function debug($msg) { /* {{{ */ 
    if(array_key_exists("debug"$_GET)) { 
        print "DEBUG: $msg<br>"
    } 
/* }}} */ 
function print_credentials() { /* {{{ */ 
    if($_SESSION and array_key_exists("admin"$_SESSION) and $_SESSION["admin"== 1) { # 세션 admin 키 값이 1 일때
    print "You are an admin. The credentials for the next level are:<br>"
    print "<pre>Username: natas21\n"
    print "Password: <censored></pre>"
    } else { 
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas21."
    } 
/* }}} */ 
 
/* we don't need this */ 
function myopen($path$name) {  
    //debug("MYOPEN $path $name");  
    return true;  
 
/* we don't need this */ 
function myclose() {  
    //debug("MYCLOSE");  
    return true;  
 
function myread($sid) {  
    debug("MYREAD $sid");  
    if(strspn($sid"1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-"!= strlen($sid)) { 
    debug("Invalid SID");  
        return ""
    } 
    $filename = session_save_path() . "/" . "mysess_" . $sid
    if(!file_exists($filename)) { 
        debug("Session file doesn't exist"); 
        return ""
    } 
    debug("Reading from "$filename); 
    $data = file_get_contents($filename);    # 파일의 내용을 문자열로 읽어들인다
    $_SESSION = array(); 
    foreach(explode("\n"$dataas $line) { # explode(기준문자열,대상문자열) : 대상 문자열을 기준 문자열로 나눈다
        debug("Read [$line]");              # 줄바꿈(\n) 을 기준으로 문자열을 나누어 $line에 저장되고
                           # $line 배열의 2개의 원소만을 $parts 배열에 저장 
    $parts = explode(" "$line2);         # $parts변수에는 공백을 기준으로 $line변수의 2개원소를 가져온다
 
    if($parts[0!= ""$_SESSION[$parts[0]] = $parts[1];     
    }                         # $parts[0]='admin', $parts[1]=1 을 조작한다면?
    return session_encode(); 
 
 
 
# myread($sid)함수
# $filename = /var/lib/php5/sessions//mysess_a0d98un6odgbf2f1sf4u6771n5 의 내용을 문자열로 읽어들여서 출력한다
# $filename 을 조작해서 natas21의 비밀번호를 알아내야한다
 
 
 
function mywrite($sid$data) {  
    // $data contains the serialized version of $_SESSION 
    // but our encoding is better 
    debug("MYWRITE $sid $data");  
    // make sure the sid is alnum only!! 
    if(strspn($sid"1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-"!= strlen($sid)) {     # $sid의 문자가 주어진 문자열에 존재하는 만큼 개수 반환
    debug("Invalid SID");                                              # $sid변수에는 숫자,알파벳으로 구성되어야 한다
        return
    } 
    $filename = session_save_path() . "/" . "mysess_" . $sid
    $data = ""
    debug("Saving in "$filename); 
    ksort($_SESSION); 
    foreach($_SESSION as $key => $value) { 
        debug("$key => $value"); 
        $data .= "$key $value\n"
    } 
    file_put_contents($filename$data); 
    chmod($filename0600); 
 
/* we don't need this */ 
function mydestroy($sid) { 
    //debug("MYDESTROY $sid");  
    return true;  
/* we don't need this */ 
function mygarbage($t) {  
    //debug("MYGARBAGE $t");  
    return true;  
 
session_set_save_handler( 
    "myopen",  
    "myclose",  
    "myread",  
    "mywrite",  
    "mydestroy",  
    "mygarbage"); 
session_start(); 
 
if(array_key_exists("name"$_REQUEST)) {     #사용자의 입력이 있다
    $_SESSION["name"= $_REQUEST["name"];     # 세션의 name키 값에 입력값 저장
    debug("Name set to " . $_REQUEST["name"]); 
 
print_credentials(); 
 
$name = ""
if(array_key_exists("name"$_SESSION)) { 
    $name = $_SESSION["name"]; 
 
<form action="index.php" method="POST"> 
Your name: <input name="name" value="<?=$name?>"><br> 
<input type="submit" value="Change name" /> 
</form> 
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div> 
</div> 
</body> 
</html> 
 
?> 
cs



3> 문제 파악하기

이번 문제의 목적

12 if($_SESSION and array_key_exists("admin"$_SESSION) and $_SESSION["admin"== 1) 를 만족시키는 것입니다

해당 페이지에서는 특이한 방식으로 세션변수를 초기화 시키고 있습니다


session_set_save_handler() 라는 구조체의 mywrite()함수와 myread() 함수를 이용해서 세션값을 정의하고 있습니다



[1] mywrite()

79    foreach($_SESSION as $key => $value) { 
80        debug("$key => $value"); 
81        $data .= "$key $value\n"
82    } 
83    file_put_contents($filename$data);


foreach 반복문을 이용해서 $data변수에 " Key Value " 형태로 데이터를 대입한다

그 다음, file에 해당 데이터들을 입력한다



[2] myread()

46 $data = file_get_contents($filename);    # 파일의 내용을 문자열로 읽어들인다
47   $_SESSION = array();
48    foreach(explode("\n"$dataas $line) { # explode(기준문자열,대상문자열) : 대상 문자열을 기준 문자열로 나눈다
49        debug("Read [$line]");               # 줄바꿈(\n) 을 기준으로 문자열을 나누어 $line에 저장되고
50                           # $line 배열의 2개의 원소만을 $parts 배열에 저장 
51    $parts = explode(" "$line2);         # $parts변수에는 공백을 기준으로 $line변수의 2개원소를 가져온다
52 
53   if($parts[0!= ""$_SESSION[$parts[0]] = $parts[1];     
54    }                          # $parts[0]='admin', $parts[1]=1 을 조작한다면?
# => $_SESSION[admin]=1 => 문제해결 !


① mywrite()함수에서 세션파일에 데이터를 입력하고, myread()함수에서는 그 파일을 읽어들인다

② explode("구분자(\n)" , 파일명 ) : 해당 파일에서 구분자를 기준으로 각 문자들을 가져온다

    즉, foreach 반복문을 통해서 세션 파일의 각 " 한 줄(\n) " 들을 $line 변수에 저장한다

$parts 배열변수$line 변수에 저장되어 있는 문자열을 " 공백 " 을 기준으로 2개 저장한다

$_SESSION[$parts[0]] = $parts[1] 을 실행해서 SESSION변수의 키와 값을 지정한다



4> 페이지에서 실습

[ 4-1 ] 세션 파일에 name admin 입력 & 세션 아이디를 부여 받기 전 


[ 4-2 ] 세션 파일에 name admin 입력 & 세션 아이디를 부여 받음


mywrite() 함수의 디버그 출력결과를 확인해보면

80   debug("$key => $value");  코드의 실행결과 : name=>admin 을 확인할 수 있다

세션파일에 " name admin \n " 해당 데이터가 입력된다


 myread()함수에서 세션 파일의 " name admin \n" 라인을 읽어들이고 

53 if($parts[0!= ""$_SESSION[$parts[0]] = $parts[1]; 을 실행한다

=> $_SESSION[name] = admin 


115 if(array_key_exists("name"$_SESSION)) { 
116     $name = $_SESSION["name"]; 
117 } 

=> ③ $name변수는 admin으로 초기화된다


119 <form action="index.php" method="POST"> 
120 Your name: <input name="name" value="<?=$name?>"><br>

=> ④ 화면의 사용자 입력창에 admin 이 써있게 된다



# 취약점은 myread()함수에서 파일의 데이터를 읽어들일 때 foreach 반복문을 사용할 때 발생한다 !



5> 입력값 변조


입력 URL : natas20....overthewire.org/index.php?name=admin%0Aadmin%201&debug 


5-1> mywrite() 동작과정

$data = name admin %0a admin 1 

file_put_contents() 함수에 의해 세션파일에 $data 값이 저장된다


5-2> myread() 동작과정

foreach( explode("\n", $data) as $line ) {

debug("Read [$line]");

$parts = explode( " ", $line, 2 ); 

    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1]; 

}


=$_SESSION[name] = admin

=> $_SESSION[admin] = 1




Username: natas21

Password: IFekPyrQXftziDEsUr3x21sYuahypdg