先知上一个大佬挖的洞,也有了简单的分析
https://xianzhi.aliyun.com/forum/topic/2135我自己复现分析过程,漏洞的原理比较简单,但是漏洞的利用方式对我而言则是一种新的利用方式。本文对分析过程做一个记录。
正文 分析软件运行的流程拿到一个需要分析的 php 程序,首先看看客户端的 http 请求是如何对应到程序中的代码的。
首先得找一个分析的开始点,就以 触发漏洞 的 请求为示例把。
GET /cash/block-printTradeBlock.html?param=eyJvcmRlckJ5IjoiaWQgbGltaXQgMCwxO3NlbGVjdCBpZigxPTIsMSxzbGVlcCgyKSkjIiB9 HTTP/1.1 Host: hack.ranzhi.top Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=2c17fafndhnfle6j4r9dskopk3; lang=zh-cn; theme=default; rid=kcvhkos22q574sqdjiha39icl5; keepLogin=false; XDEBUG_SESSION=14822 Connection: close安装系统时我们设置 www 目录为虚拟主机目录, 所以当我们访问 /cash/block-printTradeBlock.html 时,实际上访问的是 www/cash/block-printTradeBlock.html, 但是 cash 目录中并没有相关的文件
不过该目录下有 .htaccess 文件,通过该文件可以重写 url, 根据规则我们知道,如果访问 cash 目录下不存在的文件,会把请求交给 index.php 处理。
.htaccess 文件参考
https://www.zybuluo.com/phper/note/73726那下面就分析 index.php 即可,下好断点,然后发送数据包
f7 跟进 loader.php, 首先加载了一些基础类。
然后做一些初始化的操作,实例化一些基础对象,后面用来加载程序的主体
然后是设置路由方式
$app->parseRequest();会对 处理后 url 按 - 分割,第一项作为 module_name 第二项作为 method_name. 以上面的数据包为例。执行完后的结果如下。
此时我们已经设置好了 模块名 和 方法名, 下面回到 loader.php。
$common->checkPriv(); # 权限校验 $app->loadModule(); # 加载相关模块的方法进入 loadModule 方法
public function loadModule() { $appName = $this->appName; $moduleName = $this->moduleName; $methodName = $this->methodName; /* * 设置control的类名。 * Set the class name of the control. **/ $className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName; /* * 创建control类的实例。 * 根据 `$app->parseRequest()` 设置好的 模块名 实例化对应的类 * Create a instance of the control. **/ $module = new $className(); $this->control = $module; /* * 使用反射机制获取函数参数的默认值 * 通过反射获取 函数的参数名称, * 通过 `php` 的反射机制 , 获取参数名, 并且初始化好 * * */ $defaultParams = array(); $methodReflect = new reflectionMethod($className, $methodName); foreach($methodReflect->getParameters() as $param) { $name = $param->getName(); $default = \'_NOT_SET\'; if(isset($paramDefaultValue[$appName][$className][$methodName][$name])) { $default = $paramDefaultValue[$appName][$className][$methodName][$name]; } elseif(isset($paramDefaultValue[$className][$methodName][$name])) { $default = $paramDefaultValue[$className][$methodName][$name]; } elseif($param->isDefaultValueAvailable()) { $default = $param->getDefaultValue(); } $defaultParams[$name] = $default; # 组成一个由 defaultParams[参数名] = "" 构成的字典 } /** * 根据PATH_INFO或者GET方式设置请求的参数。 * 根据请求方式, 从请求数据包中获取需要的参数信息。 */ if($this->config->requestType != \'GET\') { $this->setParamsByPathInfo($defaultParams); } else { $this->setParamsByGET($defaultParams); } # 过滤数据 if($this->config->framework->filterParam == 2) { $_GET = validater::filterParam($_GET, \'get\'); $_COOKIE = validater::filterParam($_COOKIE, \'cookie\'); } /* 调用方法,并传入参数 */ call_user_func_array(array($module, $methodName), $this->params); return $module; }该函数的流程为
根据 $app->parseRequest() 设置好的 模块名 实例化对应的类
通过 php 的反射机制 , 获取方法参数名, 并且初始化好
根据请求方式, 从请求包中获取需要的参数信息到 $defaultParams 。
过滤数据
调用方法,并传入参数
看一看设置参数的方式
按照 - 分割 url格式为
module_name-method_name-param1-param2所以在该程序中 www 和 app 目录是相互对应的。
当我们请求
/dir_name/module_name-method_name-param-...-paramN.html最后会调用 app 目录下 module_name 中的 control.php 的
module_name->method_name(param1,....,paramN)比如
/cash/block-printTradeBlock.html实际就是调用 app 目录下 cash 中的 control.php 的
block->printTradeBlock()