Thinkphp3.2.3及以下版本漏洞整理

欢迎搜索公众号:白帽子左一

每天分享更多黑客技能,工具及体系化视频教程

概述

网站为了提高访问效率往往会将用户访问过的页面存入缓存来减少开销。

而Thinkphp 在使用缓存的时候是将数据序列化,然后存进一个 php 文件中,这使得命令执行等行为成为可能。

就是缓存函数设计不严格,导致攻击者可以插入恶意代码,直接getshell。

redhat6+apache2+Mysql+php5+thinkphp3.2.3

将 application/index/controller/Index.php 文件中代码更改如下:

访问 http://localhost/tpdemo/public/?username=xxx%0d%0aphpinfo();//, 即可将 webshell 等写入缓存文件。

先找到Cache.class.php文件,也就是缓存文件,关键代码:

这里读入配置,获取实例化的一个类的路径,路径是:Think\\Cache\\Driver\\

这里我尝试了var_dump($class)echo $class直接浏览器访问Cache.class.php都无法像那篇帖子一样打印出$class,后来才发现添加数据写入缓存页面跳转才打印了Think\\Cache\\Driver\\File。关键代码:

这里实例化了那个类,我们重点关注set方法,接着直接找到这个路径下的File.class.php吧。

关键代码:

这就是写入缓存的set方法,对传入的数据进行了序列化和压缩,重点看这两句:

简单拼接一下就写入文件了,Bug就出现在这里,这时来看看我们payload:

解码后就是:

就写入恶意代码了。最后看看文件名:

文件名就是md5加密值。

  • 总结:这个thinkphp缓存函数设计bug,利用起来不难,但是感觉还是挺鸡肋。
  • 原因是:1.要开启缓存2.虽然文件名是md5固定值,但是TP3可以设置 DATA_CACHE_KEY 参数来避免被猜到缓存文件名3.缓存使用文件方式4.缓存目录暴露在web目录下面可被攻击者访问。

影响版本:Thinkphp 2.x、3.0-3.1

这段代码主要就是用explodeurl拆开,然后再用implode函数拼接起来,接着带入到preg_replace里面。

preg_replace的/e模式,和php双引号都能导致代码执行的。

这句正则简化后就是 /(\\w+)\\/([^\\/\\/]+)/e注:\\w+ 表示匹配任意长的[字母数字下划线]字符串,然后匹配 / 符号,再匹配除了/符号以外的字符。其实就是匹配连续的两个参数。

eg:www.dawn.com/index.php?s=1/2/3/4/5/6每次匹配 1和2,3和4,5和6。、

\\1是取第一个括号里的匹配结果,\\2是取第二个括号里的匹配结果

也就是\\1 取的是 1 3 5\\2 取的是 2 4 6

那么就是连续的两个参数,一个被当成键名,一个被当成键值,传进了var数组里面。

而双引号是存在在 \\2 外面的,那么就说明我们要控制的是偶数位的参数。环境较为难找,本地模拟一下

本地模拟:

thinkphp是国内著名的php开发框架,有完善的开发文档,基于MVC架构,

其中Thinkphp3.2.3是目前使用最广泛的thinkphp版本,虽然已经停止新功能的开发,但是普及度高于新出的thinkphp5系列

由于框架实现安全数据库过程中在update更新数据的过程中存在SQL语句的拼接,并且当传入数组未过滤时导致出现了SQL注入。

thinkphp系列框架过滤表达式注入多半采用I函数去调用think_filter

一般按照官方的写法,thinkphp提供了数据库链式操作,其中包含连贯操作和curd操作。

在进行数据库CURD操作去更新数据的时候,利用update数据操作。

where来制定主键的数值,save方法去更新变量传进来的参数到数据库的指定位置。

通过where方法获取where()链式中进来的参数值,并对参数进行检查,是否为字符串,tp框架默认是对字符串进行过滤的。

再来到save方法,通过前面的数据处理解析服务端数据库中的数据字段信息,字段数据类型,再到_parseOptions 表达式分析,获取到表名,数据表别名,记录操作的模型名称,再去调用回调函数进入update

我们这里可以直接看框架的where子函数,之前网上公开的exp表达式注入就是从这里分析出来的结论:Thinkphp/Library/Think/Db/Driver.class.php

其中除了exp能利用外还有一处bind,而bind可以完美避开了think_filter:

这里由于拼接了$val参数的形式造成了注入,但是这里的bind表达式会引入:符号参数绑定的形式去拼接数据,通过白盒对几处CURD操作函数进行分析定位到update函数insert函数会造成sql注入,于是回到上面的update函数

Thinkphp/Library/Think/Db/Driver.class.php

可以继续跟进execute函数

这里有处对$this->queryStr进行字符替换的操作,具体是怎么更新的,我们可以进一步操作一下。

Application/Home/Controller/UserController.class.php

通过这个代码,根据进来的id更新用户的名字和钱,构造一个简单一个poc:id[]=bind&id[]=1’&money[]=1123&user=liao 当走到execute函数时sql语句为

然后$that = $this。

接下来下面的替换操作是将”:0”替换为外部传进来的字符串,这里就可控了。替换后的语句就会发现之前的user参数为:0

然后被替换为了我们传进来的东西(liao),这样就把:替换掉了。

替换后的语句:

但是id后面的 :1 是替换不掉的。

那么我们将id[1]数组的参数变为0尝试一下。

测试代码

Poc:id[]=bind&id[]=0%27&money[]=1123&user=liao

这样就造成了注入。继续构造poc。money[]=1123&user=liao&id[0]=bind&id[1]=0%20and%20(updatexml(1,concat(0x7e,(select%20user()),0x7e),1))

select 和 find 函数

以find函数为例进行分析(select代码类似),该函数可接受一个$options参数,作为查询数据的条件。

当$options为数字或者字符串类型的时候,直接指定当前查询表的主键作为查询字段:

同时提供了对复合主键的查询,看到判断:

要进入复合主键查询代码,需要满足$options为数组同时$pk主键也要为数组,但这个对于表只设置一个主键的时候不成立。

那么就可以使$options为数组,同时找到一个表只有一个主键,就可以绕过两次判断,直接进入_parseOptions 进行解析。

之后跟进_parseOptions方法,(分析见代码注释)

$options我们可控,那么就可以控制为数组类型,传入$options[‘table’]$options[‘alias’]等等,只要提层不进行过滤都是可行的。

同时我们可以不设置$options[‘where’]或者设置$options[‘where’]的值为字符串,可绕过字段类型的验证。

可以看到在整个对$options的解析中没有过滤,直接返回,跟进到底层ThinkPHP\\Libray\\Think\\Db\\Diver.class.php,找到select方法,继续跟进最后来到parseSql方法,对$options的值进行替换,解析。

因为$options[‘table’]或$options[‘alias’]都是由parseTable函数进行解析,跟进:

当我们传入的值不为数组,直接进行解析返回带进查询,没有任何过滤。

同时$options[‘where’]也一样,看到parseWhere函数

delete 函数有些不同,主要是在解析完$options之后,还对$options[‘where’]判断了一下是否为空,需要我们传一下值,使之不为空,从而继续执行删除操作。

下载地址:http://www.thinkphp.cn/download/610.html

自己配置一下数据库路径:

test_thinkphp_3.2.3\\Application\\Common\\Conf\\config.php

自己安装,安装完以后访问一下:

http://127.0.0.1/thinkphp_3.2.3/index.php/Home/Index/index没有报错就可以了。

开启debug 方便本地测试

路径:test_thinkphp_3.2.3\\index.php

路径:test_thinkphp_3.2.3\\Application\\Common\\Conf\\config.php

3处注入利用方法都是一样的,所以就演示一个 find 注入Select 与 delete 注入同理

明显转成整型,无法进行注入了。

这样就可以直接控制where了。

ThinkPHP在处理order by排序时,当排序参数可控且为关联数组(key-value)时,

由于框架未对数组中key值作安全过滤处理,攻击者可利用key构造SQL语句进行注入,该漏洞影响ThinkPHP 3.2.3、5.1.22及以下版本。

ThinkPHP3.2.3漏洞代码(/Library/Think/Db/Driver.class.php):

从上面漏洞代码可以看出,当$field参数为关联数组(key-value)时,key值拼接到返回值中,SQL语句最终绕过了框架安全过滤得以执行。

测试代码

构造poc:?order[updatexml(1,concat(0x3e,user()),1)]=1

EXP表达式支持SQL语法查询 sql注入非常容易产生。

可以改成:

exp查询的条件不会被当成字符串,所以后面的查询条件可以使用任何SQL支持的语法,包括使用函数和字段名称。

查询表达式不仅可用于查询条件,也可以用于数据更新。

表达式查询

  • $map[\’字段1\’] = array(\’表达式\’,\’查询条件1\’);$map[\’字段2\’] = array(\’表达式\’,\’查询条件2\’);$Model->where($map)->select();

跟到\\ThinkPHP\\Library\\Think\\Db\\Driver.class.php 504行

parseSQl组装 替换表达式:

parseKey()

filter_exp

I函数中重点代码:

那么可以看到这里是没有任何有效的过滤的 即时是filter_exp,如果写的是

filter_exp在I函数的fiter之前,所以如果开发者这样写I(‘get.id’, ‘’, ‘trim’),那么会直接清除掉exp后面的空格,导致过滤无效。

返回:

直接在IndexController.class.php中创建一个测试代码

数据库配置:

Poc:?id[0]=exp&id[1]==updatexml(0,concat(0x0e,user(),0x0e),0)

首先全局搜索 destruct, 这里的 $this->img 可控,可以利用其来调用 其他类的destroy() 方法,或者可以用的call() 方法,__call() 方法并没有可以利用的

那就去找 destroy() 方法

注意这里,destroy() 是有参数的,而我们调用的时候没有传参,这在php5中是可以的,只发出警告,但还是会执行。

但是在php7 里面就会报出错误,不会执行。

所以漏洞需要用php5的环境。

继续寻找可利用的delete()方法。

在Think\\Model类即其继承类里,可以找到这个方法,还有数据库驱动类中也有这个方法的,thinkphp3的数据库模型类的最终是会调用到数据库驱动类中的。

先看Model类中。

还需要注意这里!!如果没有 $options[‘where’] 会直接return掉。

跟进 getPK() 方法

$pk 可控 $this->data 可控 。

最终去驱动类的入口在这里

下面是驱动类的delete方法

我们在一开始调用Model类的delete方法的时候,传入的参数是

而后面我们执行的时候是依靠数组的,数组是不可以用 字符串连接符的。参数控制不可以利用$this->sessionName。

但是可以令其为空(本来就是空),会进入Model 类中的delete方法中的第一个if分支,然后再次调用delete方法,把 $this->data[$pk] 作为参数传入,这是我们可以控制的!

看代码也不难发现注入点是在 $table 这里,也就是 $options[‘table’],也就是 $this->data[$this->pk[‘table’]];

直接跟进 driver类中的execute() 方法

跟进 initConnect() 方法

跟进connect() 方法

数据库的连接时通过 PDO 来实现的,可以堆叠注入(PDO::MYSQL_ATTR_MULTI_STATEMENTS => true ) 需要指定这个配置。

这里控制 $this->config 来连接数据库。

driver类时抽象类,我们需要用mysql类来实例化。

到这里一条反序列化触发sql注入的链子就做好了。

poc

这里可以连接任意服务器,所以还有一种利用方式,就是MySQL恶意服务端读取客户端文件漏洞。

利用方式就是我们需要开启一个恶意的mysql服务,然后让客户端去访问的时候,我们的恶意mysql服务就会读出客户端的可读文件。

这里的hostname 是开启的恶意mysql服务的地址以及3307端口

下面搭建恶意mysql服务

修改port 和filelist

执行python脚本后,发包,触发反序列化后,就会去连接恶意服务器,然后把客户端下的文件带出来。

下面就是mysql.log 中的 文件信息(flag.txt)

当脚本处于运行中的时候,我们只可以读取第一次脚本运行时定义的文件,

因为mysql服务已经打开了,我们需要关闭mysql服务,然后才可以修改脚本中的其他文件。

然后依次 kill 就好。

PHP 中最常用的 100 个函数

PHP 静态分析引擎 Exakat 分析了 1900 个 PHP 开源项目,整理了最常用的 100 个 PHP 函数:

从这最常用的 100 个 PHP 函数,总结一下:

  • 这 100 个函数近期都没有被废弃的计划,所以可以放心使用,并加强学习。
  • 最常用的是字符串函数,然后是数组函数和文件函数,有相当多的调用是为了知道值的类型。
  • md5 是最常用的加密函数,其次是 Sha1 (#147),print_r 出现在 1/3 的项目的代码中。
  • 由于 dirname(dirname(dirname())) 的调用方式,dirname 的排名变得异常的高。
  • 在非内置库中,mbstring 排名第一、curl 第二,然后是 gd、filter 和 iconv。
  • 数组中排序中使用键比使用值更频繁。
  • 读取文件的函数比写入文件的函数应用的多,另外通常使用 file_get_contents 读取文件,使用 fwrite 写入文件。
  • array, echo, print, empty, isset 和其他语言结构,因为不能算作 PHP 函数,所以没有纳入此排名,但是它们的使用度肯定是非常高的。
  • array_push, is_object, func_get_arg, chr, call_user_func 这些函数应该用运算符替代 。
  • 数据库函数没有在这里排名,因为经常使用的是类,但数据库的功能是使用度很高的。
  • 最后许多函数在新版中有了新的功能,比如 count()dirname() 有了第二个参数,以及 preg_match()str_replace() 接受数组作为参数等。

PHP史诗级更新!8.4版本能力挽狂澜吗?

01

最新特性一览

1.1 Property Hooks 属性钩子

Property Hooks 属性钩子 可能是现代 PHP 历史上最大的变化之一:属性钩子提供对计算属性的支持,这些属性可以被 IDE 和静态分析工具直接理解,而无需编写可能会失效的 docblock 注释。此外,它们允许可靠地预处理或后处理值,而无需检查类中是否存在匹配的 getter 或 setter。

示例:

1.2 Without Parentheses 不使用括号的方法链

新版本 PHP 告别了方法链中多余的括号,开发者现在可以直接调用新实例化对象的方法时省略括号,从而简化方法链的书写。

示例代码:

以前:

现在:

1.3 Asymmetric visibility 不对称可见性

现在可以独立地控制写入属性的作用域和读取属性的作用域,减少了需要编写繁琐的 getter 方法来公开属性值而不允许从类外部修改属性的需求。

示例代码:

1.4 新的 array_*() 函数

新增函数 array_find ()、array_find_key ()、array_any () 和 array_all ()。

示例代码:

1.5 新的 HTML5 支持

PHP 8.4 添加了\\DOMDocument能够正确解析 HTML5 代码的类。旧 \\DOMDocument类仍然可用,以实现向后兼容。

示例代码:

其他核心特性更新请参考官网:https://www.php.net/releases/8.4/zh.php

02

PHP:走向没落还是迎来转机?

2.1 昔日辉煌不再

PHP 语言诞生于 1995 年,它最初只是一个处理 HTTP 表单的脚本工具。在后续的版本中增加了 MySQL 数据库查询的支持,才逐渐成为一门独立的 Web 项目开发语言。随着互联网的迅猛发展,因 PHP 易学易用和强大的开发社区而备受青睐,成为了互联网应用开发的主要语言之一。与 Linux、MySQL、Apache 合并称为 LAMP 技术栈,LAMP 对互联网的影响巨大,逐渐成为构建动态网站和应用程序的主要技术架构,据 W3C 的统计,全球有接近 78% 的 Web 网站是基于 PHP 开发构建的。

值得一提的是,PHP 以其低成本、易用、灵活性和可扩展、繁荣的 Web 技术生态,一度在编程开发领域占据着重要的地位,与 Java 语言并驾齐驱。

而随着移动互联网、云计算、人工智能等新技术的兴起,互联网软件系统变得越来越复杂。大型网站系统对于高并发、可用性的要求也越来越高。Java 相比 PHP 拥有类型安全、更好的性能、多线程连接池技术、更严格的编程规范,并且在服务治理方面拥有更成熟的解决方案和生态。很多技术团队更倾向于使用 Java 构建 Web 系统,而非 PHP。

除此之外,伴随如 Node.js、Golang 等新编程语言的出现,它们在某些方面具有 PHP 不具备的优势,例如静态编译、更好的性能和异步编程。这些编程语言也在蓬勃发展,被越来越多的开发者使用。

今年的 TIOBE 指数显示,PHP 的流行度降至了历史最低,排在第 17 名,同时,在年度 Stack Overflow 开发者调查报告中,PHP 在开发者中的受欢迎程度已经从之前的约 30% 萎缩至现在的 18%。

2.2 社区主导转型

一门编程语言的兴衰离不开它所处的环境,就像企业级编程语言的霸主 Java 在云原生时代也受到了来自 Go 语言的挑战,每年都有唱衰 Java 的言论发生,然而 Java 却仍就坚挺,但这不代表 Java 背后社区没有做出改变以面对新的时代要求。

Java 总体上是面向大规模、长时间的服务端应用而设计的,即时编译器、性能制导优化、垃圾收集子系统等 Java 最具代表性的技术特征,都是为了便于长时间运行的程序能享受到硬件规模发展的红利。但云原生时代的微服务潮流,却表示:高可用服务集群,无须追求单个服务要 7×24 小时不可间断地运行,它们随时可以中断和更新。

于是在这样的背景挑战下,Java 社区发起了 Project Leyden、Valhalla、Loom、Portola 等多个重要项目,开始了堪称航母掉头式的转型。

PHP 也是如此,本次发布的 8.4 版本,不仅仅是一系列新特性的合集,更代表着 PHP 向更现代化、高效和开发者友好的方向迈进的一大步。

国外开发者就对 PHP 的本次发版提出了向 Java 靠拢的点赞评论:

2.3 PHP 还值得学习吗?

相比现在主流的编程语言 Java、Python、C++,以及流行的 Node.js、Rust、Golang 等新型编程语言,PHP 有其无法被取代的独特优势,是一个非常便捷的开发工具集,可以帮助开发者节约很多时间。

概括而言,PHP 的优势有以下几点:

  • 语言的简单性:没有太复杂的语法、不需要考虑整型溢出、符号之类问题、没有指针概念……
  • 庞大的函数库:这些函数库涵盖了各种各样的功能和用途,让开发者能够更轻松地实现各种软件功能。
  • 强大的字符串处理能力:PHP 除了是一个编程语言之外,还是一个模版语言。可以直接在模版中嵌入 PHP 表达式。

以 Laravel、Symfony 为代表的 PHP 框架现在也越来越成熟,逐渐拉近了与 Java Spring 框架的距离。而协程扩展项目 Swoole 在今年也推出了 v6.0 的计划,为 PHP 引入了多线程+协程的并发编程方案。

对于企业选型 PHP 来说,在数千万甚至上亿用户活跃的大型 Web 系统中,PHP 技术栈可能会面临各种挑战和难题,使用 Java 或 Golang 可以容易获得更好的性能、工程规范、高并发和高可用性、更成熟的服务治理方案。但绝大部分项目不会有如此大规模的用户量级和复杂度,使用 PHP 技术栈的开发团队依然是比较有性价比的选择。研发团队可以以较少的人力资源投入保持更快的迭代速度,在当下开源节流的大趋势下尤为重要。

对于 PHP 开发者们而言,第一,我们要学习 AI,使用 ChatGPT、GitHub Copilot 等工具提升自己的开发效率,了解 Transformers 等大模型的原理;第二,我们可以使用 Docker 镜像和 Docker Swarm 容器编排工具、Docker Compose 实现本机的容器启动管理;第三,我们也要掌握 Vue/React/ElementUI 等前端技术栈,要具备全栈开发的能力;最后,学习 C++/Golang/Java 等其他编程语言技术,不仅仅局限于 PHP 一种编程语言也是十分必要的。

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。