1、payload如下:
1 | http://localhost/dede/tag_test_action.php?url=a&token=&partcode={dede:mochazz name='source' runphp='yes'}phpinfo();{/dede:mochazz} |
2、效果图:
3、具体分析:
首先,程序最开始部分有一个 CSRF 检测函数 csrf_check() ,该函数位于 dede/config.php文件中,可以看到改文件仅仅只是判断是否设置了 $token 以及 $_SESSION[‘token’] 是否与 $token 相等。然而在初始状态下, $_SESSION[‘token’] 的值为空,所以我们就可传入 $token为空,来绕过该 CSRF 检测,即http://localhost/dede/tag_test_action.php?token=
。(下面是 csrf_check() 函数,可以添加调试语句,查看初始状态下 $_SESSION[‘token’] 的值)
继续看 dede/tag_test_action.php 文件,初始状态下 $partcode 为空,这个变量又是我们可以控制的,先记下,等下要用到。
继续往下看,可以看到 $typeid 变量的值为0,所以进入27行代码 else $pv = new PartView(); 我们继续跟踪 PartView() 。
PartView() 类位于 include/arc.partview.class.php 文件第22行( 在进行代码审计的时候,可以借助linux的grep目录辅助,参数R表示递归查找,参数i表示不区分大小写,参数n表示显示第几行 )
在PartView() 类的构造函数中,又实例化一个 DedeTagParse 对象(如上图),该类用于定义dede标签格式,可以在 include/dedetag.class.php 文件第93行处找到。
回到 dede/tag_test_action.php 文件,看到程序调用了 PartView 类的 SetTemplet 方法,跟进该方法。
进入改方法后,会进入第一个if语句,并调用 DedeTagPages 类的 LoadSource 方法,继续跟进 LoadSource 方法。
可以看到(下图), LoadSource 方法先将我们 $str 写入一个inc文件,然后又调用 LoadTemplate方法,这里的$str就是我们传入的$partcode 。
LoadTemplate 先调用 SetDefault 方法做了一些初始化操作,接着进入else语句,循环读取刚刚写入文件的内容,存在 SourceString 中,然后调用 LoadCache 方法,跟进 LoadCache 方法。
LoadCache 返回false之后,就直接进入else语句,调用 ParseTemplet 方法,继续跟进。发现实例化一个 DedeAttributeParse 对象,该对象为dede的属性解析器,此时仅初始化了一些属性,暂时不跟进,继续看ParseTemplet 方法。
for循环代码太长,这里用省略号替代,主要的功能是遍历字符串模板,提取数据,然后调用 SaveCache 方法写入文件中,如下图:
下面有要回到 dede/tag_test_action.php 文件,执行最后一句 $pv->Display(); ,即调用 PartView 类的 Display 方法,发现该方法又调用了DedeTagParse 类的 Display 方法,继续跟进。
DedeTagParse 类的 Display 方法调用了 GetResult 方法,跟进
调用了 AssignSysTag 方法
在 AssignSysTag 方法中 $CTag 为 DedeTag 实例化对象,这里的 $CTag->TagName 就是 $partcode 中dede:后面的那一小串单词,这里没有写else处理语句,所以我们只要不填写 global、include、foreach、var 可以不进入if、else if语句,直接来到下面的if( $CTag->GetAtt('runphp') == 'yes' )
,跟进 GetAtt 方法(注意这里是 DedeTag 类的 GetAtt 方法,不是 DedeAttribute 类的 GetAtt 方法)
可以看到 DedeTag 类的 GetAtt 方法又调用了 DedeAttribute 类的 GetAtt 方法,继续跟进。
使用 var_dump() 来查看 $this->Items 数据类型,可以发现 $partcode 里面的name=’source’ runphp=’yes’ 会以数组键值对的形式存储在 $this->Items ,中,所以我们这里构造partcode={dede:tag name=’source’ runphp=’yes’},这样我们就进入了 AssignSysTag 方法中的 $this->RunPHP($CTag, $i) 语句。
在 RunPHP 方法中,$phpcode 在经过正则匹配后会被 eval 函数执行,而$phpcode变量是从 DedeTag->GetInnerText() 方法获取的,我们跟进 GetInnerText 方法。
其实这里的 $this->InnerText ,在前面的 LoadCache 方法中以及 DedeTag 类初始化中,可以找到定义。这个就是eval函数中 $phpcode 的值。
此次审计到此结束,整个过程有点绕,需要花一些时间,才能理清思路。