thinkphp5.0.24的学习
ThinkPHP5的学习
MVC模式
MVC是三个单词的缩写,他们分别是Model(模型),view(视图),Controller(控制),那么MVC模式就是由这三个模块组成的。面向用户的也就是最上面的那一层是视图,最下面的一层是数据,而在这两层中间的就是控制。以我们现实生活中的商店来举例,那些商铺是面向顾客的,也就是MVC中的视图,而商店的仓库就是model,那么来回于这两层之间的就是控制层。这样的化就很好的能够理解MVC模式这三层到底是干啥的了。
tp5的目录结构
根目录
在根目录下面,有application(应用目录),public(WEB目录即可通过url访问的目录),thinkphp(框架系统目录),和一些记录着整个架构用了什么库,日志和使用说明文件
application(应用目录)(APP_PATH)
这是系统默认的配置文件目录,在这个目录里面,都是模块配置文件和控制器的配置,分为应用配置(整个应用有效)和模块配置(仅针对该模块有效)。这个模块配置我觉得也可以叫做功能配置,应用配置可以叫做整个网站的配置
public(WEB目录)
这里面的文件都是可以通过web浏览器直接访问
thinkphp(框架系统目录)
在这个目录下面,都是一些对系统框架的一些定义
MVC模式的体现
在thinkphp中,由入口文件告诉控制器它需要些什么东西,然后再由控制器去调用它所需要的并且由控制器来实现用户的操作,入口文件只需要传达请求和结果即可
tp5一次请求的完整流程
首先在入口文件定义了工作目录
1 |
|
接着在入口文件处加载框架引导文件
1 |
|
接着进入框架系统目录thinkphp里的框架引导文件start.php,一进入这个文件就会先加载同目录下的base.php
1 |
|
然后进入base.php文件,前面的几十行都是在定义常量和环境常量
1 |
|
接着再载入Loader类
1 |
|
进入Loader.php,
首先就是Loader类的定义,接着就是
1 |
|
这两个函数分别使用include和require包含两个文件,然后回到base.php文件,进入一个if语句
这里会判断之前设置的ROOT_PATH常量为文件名,env为后缀的文件是否存在
如果存在的话,那么就会用parse_ini_file函数解析ROOT_PATH文件并以数组的形式传给env变量,并且会进入循环,给name赋值为配置前缀(PHP_)加上env变量的脚标全大写。接着再判断val变量是否为数组,如果是的话就把名字为item变量的值的环境变量的值赋值成name变量的值加上__和变量k的值。如果不是那么就直接将为val变量的值传给名字为name变量的值的环境变量。
自动注册加载
在这之后就会用到Loader.php里面的register方法,实现自动注册加载
register方法
注册系统自动加载
首先使用了spl_autoload_register这个函数,他会告诉thinkphp,碰到没有被实例化的类就执行loader类的autoload方法,并且会在自动注册函数失败时报出异常。
if判断语句
接着就是判断以在base.php文件里面定义的VENDOR_PATH的值为根目录,composer为子目录是否存在,如果存在,那么就会继续向下判断php的版本号是否大于5.6和判断在VENDOR_PATH目录下的composer目录是否存在autoload_static.php这个文件,如果满足这两个条件的话,那么就会包含这个php文件
然后就会给declaredclass变量赋值为当前已知类的名字所组成的数组,给composerclass变量赋值为declaredclass数组删除最后一个元素的数组。接着江foreach里面的值逐个赋值给attr变量,判断attr的值是否在composerclass的数组里面。如果不满足php的版本号大于5.6或者在VENDOR_PATH目录下的composer目录不存在autoload_static.php这个文件,那么就执行registerComposerLoader这个函数
registerComposerLoader方法
registerComposerLoader函数如图
这里有四个判断,它首先判断以VENDOR_PATH的值为目录名下的composer目录是否存在autoload_namespaces.php这个文件,如果存在就执行addPsr0这个函数
addPsr0方法
我们可以先看下autoload_namespaces.php文件
它返回了一个空数组,那么这也这也意味着namespace,path都为空。当namespace为空时,那么就会判断$fallbackDirsPsr0的值是否等于prepend变量的值,如果相等就会执行将path变量变成数组并且将$fallbackDirsPsr0的值传给path数组,否则就将paths数组的值传给$fallbackDirsPsr0。
如果prefix不为空,那么就将prefix数组的第一个值传给first变量,并且判断$prefixesPsr0[$first][$prefix]是否存在,如果不存在,那么就将$paths数组的值赋给$prefixesPsr0[$first][$prefix],如果存在的话,就判断$prefixesPsr0[$first][$prefix]值和$prepend的值是否相等,如果相等,那么就将$prefixesPsr0[$first][$prefix]的值传给$paths,否则就将$paths的值传给$prefixesPsr0[$first][$prefix]。
以VENDOR_PATH的值为目录名下的composer目录如果存在autoload_psr4.php这个文件,那么就执行addPsr4方法
addPsr4方法
还是先看一下autoload_psr4.php这个文件返回的类型是什么
可以看到仍然是以数组形式返回。
可以看到当prefix变量的值为空时,所执行的操作和addPsr0方法一样,只是把$fallbackDirsPsr0变成了$fallbackDirsPsr4
当$prefixDirsPsr4[$prefix]的值不存在的时候,判断’\\‘是否等于prefix的最后一个键值,如果不是那么就会抛出一条信息。并终止程序的运行。如果是的话那么就就将length的值传给$prefixLengthsPsr4[$prefix[0]][$prefix],并且将(array) $paths的值传给$prefixDirsPsr4[$prefix]
如果以上两种条件都不满足的话就会执行else,首先判断$prefixDirsPsr4[$prefix] 是否等于$prepend,如果等于那么就会将$prefixDirsPsr4[$prefix] 的值传给$paths,否则就将$paths的值传给$prefixDirsPsr4[$prefix],至此addPsr4方法结束
以VENDOR_PATH的值为目录名下的composer目录如果存在autoload_classmap.php这个文件,那么就执行addClassMap方法
addClassMap方法
我们先看autoload_classmap.php这个文件的返回值
很明显返回的格式为数组。
如果数组存在,那么就将class数组的值传给classmap数组。至此addClassMap方法结束
以VENDOR_PATH的值为目录名下的composer目录如果存在autoload_files.php这个文件,那么就将files数组的值等于autoload_files.php文件的返回值
那么至此,register方法的if条件就结束了。
注册命名空间定义
首先需要看下$prefixDirsPsr4数组,这是
这里使用到了loader类的addnamespace方法
其中namespace变量是一个数组,其值为
这里的if语句首先判断namespace是否是个变量,如果是的话,那么就会进入循环
这里调用了addpsr4方法
这里的prefix的值是think\,那么就会进入else条件里面,最终判断$prefixDirsPsr4[$prefix] = $prepend,那么就得到了$paths的值
当$prefix=”behavior”时,因为loader类找不到$prefixDirsPsr4[$prefix]那么就会进入elseif,此时会得到prefix变量值的长度,很明显,prefix最后一个字符就是’\‘那么就会进行两个赋值操作。
此时进行第三次循环
因为还是找不到$prefixDirsPsr4[$prefix]所以就进入elseif,进行和第二次循环一样的操作。
那么至此,命名空间定义就完成了。
接下来就是加载类库映射文件
加载类库映射文件
先判断是否存在类库映射文件,如果存在的话就就执行addclassmap方法,这里并不存在,所以就直接跳过执行loadComposerAutoloadFiles方法
loadComposerAutoloadFiles方法
进入循环,判断$GLOBALS[‘__composer_autoload_files’][$fileIdentifier]值是否为空,为空就包含$file的值,并且将$GLOBALS[‘__composer_autoload_files’][$fileIdentifier]的值变成true,剩下的也是如此
那么至此loadComposerAutoloadFiles方法执行结束
自动加载extend目录
extend目录是thinkphp5默认的扩展类库目录
这里会将**$fallbackDirsPsr4[]赋值为EXTEND_PATH**的右边去除掉DS后的字符串,那么至此register方法就执行结束了。
在base.php文件中实现了注册自动加载过后就会执行注册错误和异常处理机制
注册错误和异常处理机制
实现自动加载
进入autoload方法
先进入loader.php文件的autoload方法实现自动加载
这里会先判断**$namespaceAlias的值是否为空,如果为空,那么namespace的值就等于class变量去掉文件名的路径,继续判断$namespaceAlias[$namespace]的值是否存在,如果存在的话就给$original赋值为‘$namespaceAlias[$namespace]\class变量所指的文件名’,继续判断$original所指的类是否已经被定义了,如果定义了那么就返回将$class的值作为$original**的别名。
但是程序执行的时候**$namespaceAlias的值并不为空,所以并不会执行里面的语句,而是接着往下判断$file的值是否等于findFile($class)**的值。
我们来看一下findfile方法
进入findFile方法
首先会判断**$classMap[$class]的值是否为空,为空的话就会直接返回,不为空的话就会继续往下走,将$logicalPathPsr4赋值为将$class变量里的\用DS替换后的字符串加.EXT,比如程序运行到这里的$class的值是think\error,替换过后$logicalPathPsr4的值为think\error.php,接着再给$first赋值为$class[0],接着再向下判断$prefixLengthsPsr4[$first]的值是否存在,如果存在就会进入循环,判断$prefix首次出现的位置是不是在$class的开头,如果是就进入内部循环不是就进行下一次循环。程序运行到这里的时候$class的值为”think\error”,那么当$prefix的值为”think”的时候就会进入内部循环,在内部循环里面,会将$prefixDirsPsr4[$prefix]的值作为$dir的值,进行判断,$file的值是否等于$dir加上$logicalPathPsr4的值去掉前$length后的值,如果相等就会返回$file**,程序运行到这里的时候,相关的各值为
可以很明显的看到相等
那么就会回到autoload方法
返回autoload方法
此时**$file**的值等于findFile方法返回的值,进入if语句中
接着进行判断如果是非win环境下或者判断**$file的文件名与绝对路径下的$file**的文件名是否像等,如果满足了其中的一个条件那么就进入里面的语句,程序进入里面的语句后会此案执行__include_file方法
他会返回包含**$file**文件,然后autoload方法就会返回true
进入Erorr.php文件的register方法
这些全都是设置当程序出错了会报什么错误信息,没什么好看的
加载惯例配置文件
进入convention.php文件
程序并没有直接进入config文件,而是先进入convention文件返回了应用设置参数
进入config.php文件的set方法
此时**$name**的值为
首先给**$range**赋值成
然后进入判断,如果**$config[$range]并不存在,那么就会让$config[$range]为空数组,此时程序继续进入下一个条件语句判断$name是否为字符串,很明显$name是一个数组,所以跳过这次if,继续进入下一个if的判断,判断$name是不是数组,刚刚已经说了,$name是一个数组,那么就进入if里面的语句,继续判断$value的值是否为空,如果为空就跳过这个if,不为空就执行if里面的语句。程序进行到这里时$value的值为空,所以直接跳过了,接着就直接返回$config[$range]和$name合并的数组并且将$name**的所有键大写。
至此config.php和base.php都结束了
进入start.php
再次调用autoload方法
和之前调用的一样,调用过后进入app.php的run方法
进入app.php的run方法
首先判断**$request变量是否为空,如果为空,就执行request类的instance方法,否则就是其本身,很明显这里$request**为空,那么就执行一次autoload方法再进入request.php文件
进入request.php文件
这里先判断**$instance是否为空,如果为空就让$instance**为static对象,执行__construct魔术方法
执行__construct魔术方法
判断filter对象是否为空,为空就将其执行config类的get方法
get方法
先给**$range赋值为”_sys_“,然后判断$name的值是否为空和$config[$range]是否存在,很明显$name**的值不为空,所有直接进入下一个判断
这个判断.在**$name**里面是否存在,如果不存在就就执行里面的语句,存在就跳过,这里很明显不存在,所有执行里面的语句
首先将**$name的字符全变成小写,然后如果$config[$range][$name]存在,那么就返回$config[$range][$name]**,否则返回null,这里并不存在,所有最后的filter的值为空
然后执行
然后返回**$instance**
返回app.php的run方法
给**$config**变量赋值为initcommon方法的返回值
进入initcommon方法
首先判断**$init的值是否为空,程序运行到这里确实为空,执行里面的语句,判断是否定义了APP_NAMESPACE**这个常量,并没有定义直接跳过,运行Loader类的addnamespace方法
进入addnamespace方法
先判断**$namespace**是否是一个数组,这里很明显不是,直接跳过,执行addpsr4方法
执行addpsr4方法
执行方法和之前是一样的,就不用再细讲了
这里是各变量的值
返回initcommon方法
执行完addpsr4方法就直接返回到initcommon方法并且继续向下执行
进入init方法
首先先定位模块目录,然后再判断APP_PATH . $module . ‘init’ . EXT这个文件存不存在
存在的话就会包含这个文件,elseif也一样,如果存在RUNTIME_PATH . $module . ‘init’ . EXT这个文件就会包含这个文件
但是程序执行到这并没发现这两个文件,所以就执行else
先用config类的load方法给**$config**赋值
进入load方法
首先给**$range赋值为”_sys_“,然后进行判断,如果不存在$config[$range]这个变量,那么就令这个变量为空数组,存在就进行下一个判断。这里是存在的,进行下一个判断,程序是存在$file值的文件的,那么就将$name的字符全都变成小写,并且将$type**的值赋值为文件的后缀,也就是php,那么就满足下面一个条件,就会返回set方法的返回值
进入set方法
首先给**$range赋值为”_sys_“,然后进行判断,如果不存在$config[$range]这个变量,那么就令这个变量为空数组,存在就进行下一个判断。这里是存在的,进行下一个判断,很明显$name是一个数组,那么就执行180行的代码,当程序运行到这里的时候,$value的值为空,不满足这个if条件并且跳过这个if语句,直接返回$config[$range]的值,$config[$range]的值是由$config[$range]和$name结合在一起的新数组,并且原$name**的键全大写
返回app.php文件
接着就往下执行,给**$filename赋值为CONF_PATH . $module . ‘database’ . CONF_EXT**,接着进入config类的load方法,进入load方法知乎再进入set方法,和刚才的一摸一样,只是这里的$value变量值不玩i空,所以会执行if里面的语句
先判断**$config[$range]存不存在,如果存在,那么就返回$config[$range]的值是由$config[$range]和$name结合在一起的新数组,不存在就返回$name**数组的值,然后又返回app.php。
接着往下判断,因为存在CONF_PATH . $module . ‘extra’这个目录,所以执行if里面的语句,首先给$dir赋值为
CONF_PATH . $module . ‘extra’然后给$files数组赋值为**$dir目录下的子目录和文件,然后进行循环,判断.加上文件的后缀名是否与CONF_EXT相等,相等就执行里面的代码,给$filename赋值为$file**的绝对路径,然后调用load方法,所执行的代码和249行代码执行load方法一样,就不多阐述了。
接下来判断出**$config[‘app_status’]的值并不存在,于是就跳过这个if,看下面的if,这个if判断出CONF_PATH . $module . ‘tags’ . EXT**这个文件存在,于是就执行Hook类的import方法,因为之前没有定义这个类,所以需要走一遍autoload方法
进入autoload方法
执行的流程还是和之前的一样
进入Hook类import方法
$recursive的值为true,执行条件语句中,进行循环
进入Hook类add方法
这里判断**$behavior是不是一个数组和它能否在当前环境使用,这里满足了条件,进行下一个判断,判断出$behavior里并不存在’_overlay’这个键,执行if里面的语句,首先释放掉$behavior[‘_overlay’]**的值,并且将
$tags[$tag]变为由$tags[$tag]和$behavior所组成的数组,剩下的执行顺序和代码都是一样的、
返回app.php文件
这里将**$path的值赋值为APP_PATH . $module,往下进行判断出存在$path . ‘common’ . EXT这个文件,所以包含这个文件,由于$module**的值为空,所以就跳过if里面的代码,直接返回get方法的返回值。
进入get方法
可以看到,刚刚什么都没传给get方法,所以是无参数,直接返回**$config[$range]**的值
返回App类的initcommon方法
给**$suffix赋值为$config[‘class_suffix’]的值,然后调用了Env类的get方法和config类的get方法给$debug**赋值
进入env类的get方法
因为是第一次进入,所以还是得调用loader类的autoload方法然后再进入env类
首先给**$result赋值,判断出$result等于false然后返回$default**的值
调用config的get方法
还是首先给**$range赋值,然后判断出$name中没有出现.并执行里面的代码,先给$name**数组的键全都变成小写,然后判断是否存在$config[$range][$name],如果存在就返回$config[$range][$name],不存在就返回null
返回App类的initcommon方法
这里判断出**$debug**存在并且为假,执行里面的语句,再php.ini文件里将’display_errors’的值设置成off,然后再继续往下判断
然后判断出**$config[‘root_namespace’]的值为空,跳过这个判断语句,然后再往下判断
判断出$config[‘extra_file_list’]的值不为空,执行循环,首先判断出$file里是否存在.如果存在就把$file的值传给$file,不存在就把APP_PATH . $file . EXT的值传给$file,接着判断$file的值这个文件是否存在和$file[$file]是否存在,这里判断出前者存在,后者不存在满足条件,包含$file文件,并且将$file[$file]**赋值为true。
接着设置系统时区为**$config[‘default_timezone’]**的值,然后调用Hook类的listen方法
进入Hook类的listen方法
首先定义**$results**为空数组,然后使用同类的get方法
先检查**$tags数组里面是否有$tag这个键,如果有就返回$tags[$tag]**的值,否则就返回空数组
然后listen方法返回 $results的值
返回App类的initcommon方法
把**$init**的值设置为true,然后返回config类的get方法的返回值
返回**$config[$range]**的值
退出App类的initcommon方法
然后继续往下走
!
判断出BIND_MODULE这个常量并不存在,所以直接跳过
然后给**$request**赋值为filter方法的返回值
进入Request类的filter方法
判断出**$filter的值并不是null值,然后就将request类的filter对象赋值为$filter的值,那么最终$request**的值就是request类的filter对象的值
然后执行Lang类的range方法
进入Lang类的range方法
直接返回**$range**的默认值
返回app类的run方法
首先开启多语言机制然后检测当前语言,然后给**$request**赋值为langset方法的返回值,首先进入lang类的range方法
返回**$range**的空值,然后进入request类的langset方法
判断出**$lang的值并不是空值,执行条件语句,给当前类的langset对象赋值为$lang**的值然后返回
然后app类的run方法继续往下走
首先使用request类的langset方法来进行赋值,赋值完成后进入lang类的load方法
先给**$range赋值,然后判断$file**的值是不是字符串,如果是的话,就变成数组,不是那么就不做改变,很明显这里不是字符串
然后判断出**$lang[$range]并不存在,于是就令$lang[$range]的值为空数组,然后再让$lang的值为空数组,进入循环,让$file的每一个键值等于$_file**,然后判断**$_file所代表的文件存不存在,这里判断出存在,执行记录加载信息,并且包含了$_file所代表的文件,然后判断出$_array是一个数组,然后让$lang变成一个由$_lang数组的键全大写的数组和$lang**数组组成的新数组。
接着判断出**$lang不为空值,然后让$lang[$range]的值等于$lang的值加上$lang[$range]的值,最后返回$lang[$range]**的值。
然后返回app.run继续向下执行
这里调用了hook类的listen方法
进入hook类的listen方法
首先令**$results**为一个空数组,然后进入循环,先使用了get方法
返回**$tag的值,然后再返回$results**的值
返回app.run方法
接着给**$dispatch赋值,并且出判断$dispatch**的值为空,执行routecheck方法
进行routecheck方法
首先给**$path**变量利用path方法赋值
首先判断出path的值为空,就使用config类的get方法
首先给**$range赋值为”_sys_“然后判断出$name**的值里面并没有.
执行if语句,首先将**$name数组的键都变成小写,然后再判断$config[$range][$name]这个值是否存在,如果存在就返回$config[$range][$name]**的值,不存在就返回null,然后返回path方法
再调用pathinfo方法
首先判断出pathinfo对象的值为空,然后再判断url里面并不存在兼容模式参数
接着再判断出url里存在‘PATH_INFO’然后再判**断$_SERVER[‘PATH_INFO’]**的值是否为空,如果为空就返回’/‘,不为空就返回$_SERVER[‘PATH_INFO’]**变量左边移除掉’/‘后的值,最后返回值为’/‘。
回到path方法
判断出**$suffix**存在且不为false,然后去除掉正常的url后缀,最后返回path。
然后回到routecheck方法
给**$depr赋值为$config[‘pathinfo_depr’]的值,然后再给$result**赋值为false。
然后判断**$routeCheck这个值是否为空,如果不为空就给$check赋值为$routeCheck否则就赋值为$config[‘url_route_on’],然后判断出$check变量存在,继续判断出RUNTIME_PATH . ‘route.php’这个文件并不存在,进入else条件,首先给$files赋值为$config[‘route_config_file’]**,然后进入循环
判断出CONF_PATH . $file . CONF_EXT文件存在,包含CONF_PATH . $file . CONF_EXT文件,然后使用route类的import方法进行导入路由配置,然后再使用route类的check方法
首先判断出**$debug && Config::get(‘route_check_cache’)为真跳过这个if,然后将$url里的’/‘替换成|然后再赋值给$url,然后判断出$rules[‘alias’][$url]并不存在,然后给$method**进行赋值并且将键全转为小写。
然后判断**$rules[$method]变量是否存在,如果存在就给$rules赋值为$rules[$method],不存在就赋值为空数组。然后判断出$checkDomain为false,给$return赋值为checkUrlBind($url, $rules, $depr)**的返回值
首先判断出**$bind为空,所有直接返回false,那么$return**的为false,那么就直接跳过if语句
给**$item赋值为将$url中的’|’转换为’/‘后的值,然后判断出并不存在$rules[$item],然后再判断出$rules的值不为空,返回checkRoute($request, $rules, $url, $depr)**的返回值。
然后判断**$routeMust的值是否为空,如果为空那么就给$must赋值为$config[‘url_route_must’],不为空就赋值为$routeMust。然后判断出$result等同于false,然后给$result赋值为parseUrl($path, $depr, $config[‘controller_auto_search’])的返回值,最后返回$result**的值
返回app.run方法
调用dispatch方法
判断出**$dispatch不为空,并返回$dispatch的值,然后判断出$debug**的值为false,就向下执行hook类的listen方法
首先给**$results定义为空数组,然后判断出$once为假,返回$results**的值
然后执行cache方法,发现是直接退出来了。
继续向下执行exec方法
继续调用module方法,给**$data**赋值,然后调用loader类的clearInstance方法
将**$instance**设为空数组
然后输出数据到客户端
$data的值不为空,所以执行elseif的语句
默认自动识别响应输出类型
然后再执行hook类的listen函数
和之前走的流程一样,最后返回**$response**的值,至此,app.run方法执行完毕
进入app.send方法
首先使用hook类的listen方法,流程仍然是一样的。然后使用getContent方法
判断出content对象的值为空,给**$content**赋值为output方法的返回值
判断出if条件里的语句为假,故跳过并返回将**$content**变成字符串后的值
然后继续往下执行
调用两个方法给$cache赋值,然后判断出cache的值为false,进行下一个判断,判断出**headers_sent()**为假和header对象不为空,然后发送状态码和头部信息
然后打印出**$data**,然后判断出fastcgi_finish_request这个函数并没有定义,然后调用hook类的listen方法,仍然和之前一样,然后判断出**($this instanceof RedirectResponse)**为false,就调用seesion的flush方法
首先判断出**$init**并不存在,直接退出。至此依次请求的完整流程就结束了
thinkphp5的反序列化漏洞
为了用反序列化触发rce,那么就需要调用Request
类__call
方法.
二次开发
由于下载下来的tp并没有反序列化入口,所以我们得在index的controller中加入代码
1 |
|
pop链构造分析
首先,进行全局搜索__destruct,查看thinkphp/library/think/process/pipes/Windows.php的Windows类中调用了__destruct魔术方法。
1 |
|
根据removeFiles,发现了file_exists函数,file_exists函数处理的时候会将对象当做字符串处理,那么这就会触发__toString函数
跳板利用点:thinkphp/library/think/Model.php
__toString方法
1 |
|
跟进tojson方法
1 |
|
跟进toarray方法
存在三处地方可以执行__call方法
1 |
|
1 |
|
1 |
|
由于我们的目的是调用Output类的__call
且能够继续利用,调试后选择第三处当做调板
那么我们怎样能够执行到这行代码呢
先溯源一下溯源$values
变量,比较关键是下面两行
1 |
|
跟进一下getRelationData
方法
1 |
|
看一下这三个条件
1,$this->parent
存在且可控
2,!$modelRelation->isSelfRelation()
跟进isSelfRelation方法
1 |
|
返回$this->selfRelation,可控
3,get_class($modelRelation->getModel()) == get_class($this->parent)
跟进一下getModel方法
1 |
|
所以我们得找一下哪一个getModel可,全局搜索找到了thinkphp\library\think\db\Query.php
1 |
|
那么这三个条件都满足,执行$value = $this->parent; return $value
为了执行__call方法,那么我们还得看一下如何绕过那几个if语句
第一个if
需要满足modelRelation这个类中存在getBindAttr()函数,而且下一个bindAttr
是该函数的返回值
全局搜索getBindAttr方法,在 OneToOne类找到可控的getBindAttr方法
1 |
|
可以看到onetoone类继承了Relation类,搜索一下继承了onetoone类
那么我只需要让$modelRelation
是hasne类即可
其实下面还有一个if,但是我们简单看下,将$bindAttr的键值对中的键给$key,然后进行isset判断,当已经定义才满足if,我们要进入的是不满足if条件的时候
然后进入__call,要选择一个能写webshell的类的__call方法,选择了thinkphp\library\think\console\Output.php
所以上面的$value应该是一个thinkphp\library\think\console\Output.php类对象
在这里method和this->styles是可控的,array_unshift()对调用block()方法没有影响,可以执行block方法,跟进Output的block方法
1 |
|
跟进writeln方法
1 |
|
跟进write方法
1 |
|
handle属性可控,所以全局搜索write
方法thinkphp\library\think\session\driver\Memcached.php
的write方法
1 |
|
而$this->handler可控,所以全局搜索可用的set方法
在thinkphp\library\think\cache\driver\File.php
中,set方法可以使用file_put_contents
写文件,第158行的exit可以使用伪协议进行绕过
结果发现file_put_contents可以写文件,但是内容不可控
继续看set接下来的代码,调用了setTagItem()
进入thinkphp\library\think\cache\Driver.php的setTagItem方法,File类继承了Driver类,但是Driver是一个抽象类,并且会再执行一次set方法,这一次$key是由$this->tage而来,可控;$value由$name而来,也是可控的,那么就可以编写exp了
exp(借鉴的网上的exp)
1 |
|
ps:注意需要将php的short_open_tag
设为Off
,不然会将<??>
之间的内容识别为php代码,但是<? 之后是cuc,不符合语法,所以报错
pop链图
总结:对pop链的有些利用方式还是没太懂,因为这周的前几天一直在看newstar的题就导致看thinkphp的时间不够了,只有这样写了……..
参考链接: