svg的深度利用绕过waf

news2024/9/24 5:29:30

目录

DOM树的构建

源码

img失败的原因

解法一 --- svg成功的原因

触发流程

实验

解法二 --- details标签

事件触发流程

实验验证

总结


首先在解这道题的开始连了解下什么是DOM树,以及DOM树的构建

DOM树的构建

我们知道JS是通过DOM接口来操作文档的,而HTML文档也是用DOM树来表示。所以在浏览器的渲染过程中,我们是最关注的就是DOM树是如何构建的。

首先在解析一份文档时,先由标记生成器做词法分析,将读入的字符转化为不同类型的Token,然后将Token传递给树构造器处理;接着标识识别器继续接收字符转换为Token,如此循环。实际上对于很多其他语言,词法分析全部完成后才会进行语法分析(树构造器完成的内容),但由于HTML的特殊性,树构造器工作的时候有可能会修改文档的内容,因此这个过程需要循环处理。

在树构建过程中,遇到不同的Token有不同的处理方式。

具体的判断是在HTMLTreeBuilder::ProcessToken(AtomicHTMLToken*token)中进行的。AtomicHTMLToken是代表Token的数据结构,包含了确定Token类型的字段,确定Token名字的字段等等。Token类型共有7种,kStartTag代表开标签,kEndTag代表闭标签,kCharacter代表标签内的文本。所以一个<script>alert(1)</script>会被解析成3个不同种类的Token,分别是kStarTag、kCharacter和kRendTag。在处理Token的过程中,还有一个InsertionMode的概念,用于判断和辅助处理一些异常情况。

在处理Token的时候,还会用到HTMLElementStack,一个栈的结构。当解析器遇到开标签时,会创建相应元素并附加到其父节点,然后将Token和元素构成的Item压入该栈。遇到一个闭标签的时候,就会一直弹出站直到遇到对应元素构成的item为止,这也是一个处理文档异常的办法。比如<div><p>1</div>会被浏览器正确识别达成<div><p>1</p></div>正是借助了栈的能力。

而当处理script的闭合标签是,除了弹出相应item,还会暂停当前的DOM树构建,进入JS的执行环境。换句话说,在文档中的script标签会阻塞DOM的构造。JS环境里对DOM操作又会导致回流,为DOM树构造造成额外影响。

源码

<script>
 const data = decodeURIComponent(location.hash.substr(1));;
 const root = document.createElement('div');
 root.innerHTML = data;

 // 这里模拟了XSS过滤的过程,方法是移除所有属性,sanitizer
 for (let el of root.querySelectorAll('*')) {
  let attrs = [];
  for (let attr of el.attributes) {
   attrs.push(attr.name);
  }
  for (let name of attrs) {
   el.removeAttribute(name);
  }
 }    
  document.body.appendChild(root); 

</script>

可以看出这是个明显的DOM XSS,用户的输入会构成一个新的div元素的子节点,但在插入body之前会被移除所有的属性。

了解完上述的内容后,我们可以看看为什么svg可以成功,而img为什么失败。

img失败的原因

先来找一下失败案例的原因,看看是哪里触发了img payload的事件代码。将过滤的代码注释以后,注入payload并打断点调试一下。

 可以发现即使代码已经执行到最后一步,但在没有退出JS环境以前依然还没有弹窗。

 此时再点击单步调试就会来到我们的代码的执行环境了。此外,这里还有一个细节就是appendChild被注释并不影响代码的执行,证明即使img元素没有被添加到DOM树也不影响相关资源的加载和事件的触发。

那么很显然,alert(1)是在页面上script标签中的代码全部执行完毕以后才被调用的。这里涉及到浏览器渲染的应一部分内容:

在DOM树构建完成以后,就会触发DOMContentLoaded事件,接着加载脚本、图片等外部文件,全部加载完成之后触发load事件。

同时,上文已经提到了,页面的JS执行时会阻塞DOM树构建的。所以总的来说,在script标签内的JS执行完毕以后,DOM树才会构建完成,接着才会加载图片,然后发现加载内容出错才会触发error事件。

可以在页面上添加以下代码来测试这一点。

  window.addEventListener("DOMContentLoaded", (event) => {
    console.log('DOMContentLoaded')
  });
  window.addEventListener("load", (event) => {
    console.log('load')
  });

测试结果:

那么失败的原因也就跟明显了,由于JS阻塞DOM树,一直到JS语句执行结束后,才可以引入img,此时img的属性已经被sanitizer清除了,自然也不可能执行事件代码了。 

总结下整个执行流程就是:

最先执行的js ---> for循环用来删除我们标签属性的 ---> js把dom树阻塞住了,js先执行 ---> img所有的属性先删除了 ---> js执行结束 ---> 恢复DOM树的加载 ---> DOMCONTENTloaded ---> 此时img已经没有任何属性了

解法一 --- svg成功的原因

payload

<svg><svg onload=alert(1)>

继续使用断电调试svg payload为何成功。

在root.innerHtml = data断下来后,点击单步调试。

 然而神奇的事情发生了,直接弹出了弹窗,点击确认以后,调试器才会走到下一行代码。而且,这个地方如果只有一个<svg οnlοad=alert(1)>,那么结果将同img一样,直到script标签结束以后才能执行相关的代码,这样的代码放在挑战里也将失败(测试单个svg时要注意,不能想img一样注释掉appendChild那一行)。那为什么多了一个svg套嵌就可以提前执行呢?带着这个疑问,我们来看一下浏览器是怎么处理的。

触发流程

上文提到一个叫HTMLElementStack的结构用来帮助构建DOM树,它有多个出栈函数。其中,除了Pop All以外,大部分出栈函数最终会调用到PopCommon函数。这两个函数代码如下:

void HTMLElementStack::PopAll() {
  root_node_ = nullptr;
  head_element_ = nullptr;
  body_element_ = nullptr;
  stack_depth_ = 0;
  while (top_) {
    Node& node = *TopNode();
    auto* element = DynamicTo<Element>(node);
    if (element) {
      element->FinishParsingChildren();
      if (auto* select = DynamicTo<HTMLSelectElement>(node))
        select->SetBlocksFormSubmission(true);
    }
    top_ = top_->ReleaseNext();
  }
}

void HTMLElementStack::PopCommon() {
  DCHECK(!TopStackItem()->HasTagName(html_names::kHTMLTag));
  DCHECK(!TopStackItem()->HasTagName(html_names::kHeadTag) || !head_element_);
  DCHECK(!TopStackItem()->HasTagName(html_names::kBodyTag) || !body_element_);
  Top()->FinishParsingChildren();
  top_ = top_->ReleaseNext();

  stack_depth_--;
}

当我们没有正确闭合标签的时候,如<svg><scg>,就可能调用到PopAll来清理;而正确闭合的标签就可能调用到其他出栈函数并调用到PopCommom。这两个函数有一个共同点,都会调用栈中元素的FinishParsingChildren函数。这个函数用于处理叶子节点解析完毕以后的工作。因此,我们可以查看svg标签对应的元素类的这个函数。

void SVGSVGElement::FinishParsingChildren() {
  SVGGraphicsElement::FinishParsingChildren();

  // The outermost SVGSVGElement SVGLoad event is fired through
  // LocalDOMWindow::dispatchWindowLoadEvent.
  if (IsOutermostSVGSVGElement())
    return;

  // finishParsingChildren() is called when the close tag is reached for an
  // element (e.g. </svg>) we send SVGLoad events here if we can, otherwise
  // they'll be sent when any required loads finish
  SendSVGLoadEventIfPossible();
}

这里有一个非常明显的判断IsOutermostSVGSVGElement,如果是最外层的svg则直接返回。注释也告诉我们了,最外层svg的load事件由LocalDOMWindow::dispatchWindowLoadEvent触发;而其他svg的load事件则在达到结束标记的时候触发。所以我们跟进SendSVGLoadEventIfPossible进一步查看。

bool SVGElement::SendSVGLoadEventIfPossible() {
  if (!HaveLoadedRequiredResources())
    return false;
  if ((IsStructurallyExternal() || IsA<SVGSVGElement>(*this)) &&
      HasLoadListener(this))
    DispatchEvent(*Event::Create(event_type_names::kLoad));
  return true;
}
先决条件 在于svg不能最外层 onload 必须保证不是最外层

这个函数是继承自父类SVGElement的,可以看到代码中的DispatchEvent(*Event::Create(event_type_names::kLoad));确实触发了load事件,而前面的判断要满足是svg元素以及对load事件编写了相关代码即可,也就是说在这里执行了我们写的οnlοad=alert(1)的代码。

总结

svg之所以可以成功实现,原因是,当遇到外层的svg时直接返回了,但是遇到内层svg时,且存在onload事件则直接执行,使用到的是DispatchEvent(*Event::Create(event_type_names::kLoad));函数。

实验

我们可以将过滤的代码注释,并添加相关代码来验证这个事件的触发时间。

  window.addEventListener("DOMContentLoaded", (event) => {
    console.log('DOMContentLoaded')
  });
  window.addEventListener("load", (event) => {
    console.log('load')
  });

同时,我们将注入代码也再套嵌一层

<svg onload=console.log("svg0")><svg onload=console.log("svg1")><svg onload=console.log("svg2")>

可以看到结果不出所料,最内层的svg先触发,然后再到下一层,而且是在DOM树构建完成以前就触发了相关事件;最外层的svg则得到等到DOM树构建完成才能触发。

解法二 --- details标签

payload

<form tabindex=1 onfocus="alert(1);this.removeAttribute('onfocus');" autofocus=true> <img id=attributes><img id=attributes></form>

details存在有时可行,有时又不行的问题,现在我们进行探讨下

<details open ontoggle=alert(1)>

事件触发流程

首先触发代码的点是在DispatchPendingEvent函数里

void HTMLDetailsElement::DispatchPendingEvent(
    const AttributeModificationReason reason) {
  if (reason == AttributeModificationReason::kByParser)
    GetDocument().SetToggleDuringParsing(true);
  DispatchEvent(*Event::Create(event_type_names::kToggle));
  if (reason == AttributeModificationReason::kByParser)
    GetDocument().SetToggleDuringParsing(false);
}

而这个函数是在ParseAttribute被调用的

void HTMLDetailsElement::ParseAttribute(
    const AttributeModificationParams& params) {
  if (params.name == html_names::kOpenAttr) {
    bool old_value = is_open_;
    is_open_ = !params.new_value.IsNull();
    if (is_open_ == old_value)
      return;

    // Dispatch toggle event asynchronously.
    pending_event_ = PostCancellableTask(
        *GetDocument().GetTaskRunner(TaskType::kDOMManipulation), FROM_HERE,
        WTF::Bind(&HTMLDetailsElement::DispatchPendingEvent,
                  WrapPersistent(this), params.reason));

    ....

    return;
  }
  HTMLElement::ParseAttribute(params);
}

ParseAttribute正是在解析文档处理标签属性的时候被调用的。注释也写到了,分发toggle事件的操作是异步的。可以看到下面的代码是通过PostCancellableTask来进行回调出发的,并且传递了一个TaskRunner。

TaskHandle PostCancellableTask(base::SequencedTaskRunner& task_runner,
                               const base::Location& location,
                               base::OnceClosure task) {
  DCHECK(task_runner.RunsTasksInCurrentSequence());
  scoped_refptr<TaskHandle::Runner> runner =
      base::AdoptRef(new TaskHandle::Runner(std::move(task)));
  task_runner.PostTask(location,
                       WTF::Bind(&TaskHandle::Runner::Run, runner->AsWeakPtr(),
                                 TaskHandle(runner)));
  return TaskHandle(runner);
}

跟进PostCancellableTask的代码则会发现,回调函数(被封装成task)正是通过传递的TaskRunner去派遣执行。

清楚调用流程以后,就可以思考,为什么无法触发这个事件呢?最大的可能性,就是在任务交给TaskRunner以后又被取消了。因为是异步调用,而且PostCancellableTask这个函数也暗示了这一点。

实验验证

可以做一个实验来验证,修改小挑战代码,将sanitizer部分延时执行。

 const data = decodeURIComponent(location.hash.substr(1));;
 const root = document.createElement('div');
 root.innerHTML = data;
 setTimeout( () => {
     for (let el of root.querySelectorAll('*')) {
      let attrs = [];
      for (let attr of el.attributes) {
       attrs.push(attr.name);
      }
      for (let name of attrs) {
       el.removeAttribute(name);
      }
     }    
     document.body.appendChild(root)
 } , 2000)

修改代码前

执行失败

修改代码后

可以看到,确实成功执行了事件代码。

那么回头来想一下,为什么测试Tui的时候直接成功,却在修改前的挑战代码中失败?看一下Tui的处理这部分内容的相关代码。tui.editor/htmlSanitizer.ts at 48a01f5add76cb6eedb29cceb95f765164d69649 · nhn/tui.editor · GitHub

export function sanitizeHTML(html: string) {
  const root = document.createElement('div');

  if (isString(html)) {
    html = html.replace(reComment, '').replace(reXSSOnload, '$1');
    root.innerHTML = html;
  }

  removeUnnecessaryTags(root);
  leaveOnlyWhitelistAttribute(root);

  return finalizeHtml(root, true) as string;
}

sanitizeHTML函数是处理用户输入的部分。比起挑战的代码,这里多了正则过滤,移除黑名单标签(removeUnnecessaryTags),不过不会移除所有标签而是留下了部分白名单标签(leaveOnlyWhitelistAttribute)。最神奇的部分来了,details标签也是黑名单的一员,这也是我一开始无法理解为何这个payload能够成功执行的原因。但现在我们清楚调用流程以后,可以有一个大胆的猜测:正是因为detalils在黑名单里,所以被移除以后其属性没有被直接修改,所以事件依然在队列中没有被取消

再进行一个实验来验证,对挑战的代码做一些修改,增加移除标签的代码。

 const data = decodeURIComponent(location.hash.substr(1));;
 const root = document.createElement('div');
 root.innerHTML = data;

 let details = root.querySelector("details")
 root.removeChild(details)

 for (let el of root.querySelectorAll('*')) {
  let attrs = [];
  for (let attr of el.attributes) {
   attrs.push(attr.name);
  }
  for (let name of attrs) {
   el.removeAttribute(name);
  }
 }

成功执行了代码

得出结论,details标签的toggle事件是异步触发的,并且直接对details标签的移除不会清除原先通过属性设置的异步任务

总结

对于DOM XSS,我们是通过操作DOM来引入代码,但由于浏览器的限制,我们无法像这样root.innerHTML = "<script>..</script>"直接执行插入的代码,因此,一般需要通过事件触发。通过上面的例子,可以发现依据事件触发的时机能进一步区分DOM XSS:

1、立即型,操作DOM时触发。嵌套的svg可以实现

2、异步型,操作DOM后,异步触发。details可以实现

3、滞后型,操作DOM后,由其他代码触发。img等常见payload可以实现

从危害来看,明显是1>2>3,特别是1,可以直接无视后续的sanitizer操作 

方法三 --- 绕过过滤(DOM破坏)

绕过过滤主要是为了使得Payload里面的属性不被清除,最终触发事件执行JS。

payload

<form tabindex=1 onfocus="alert(1);this.removeAttribute('onfocus');" autofocus=true> <img id=attributes><img id=attributes></form>

劫持DOM 

测试

首先我们尝试使用一个img标签来看看,浏览器会给我们返回什么结果?

payload

<form tabindex=1 onfocus="alert(1);this.removeAttribute('onfocus');" autofocus=true> <img id=attributes></form>

 可以看到浏览器此时弹出了报错信息,说el.attributes不是一个可迭代对象,而此时已经可以认为我们劫持了DOM。为什么会这么说呢?以python来举例,我们都知道在程序当中,只有可迭代对象才能进入for循环,而为什么会报错呢?因为此时的img只有一个不能被迭代,所以会弹出说el.attributes不是一个可迭代对象。

测试

 看到上面的结果,很明显的看到只有设置为id属性的时候,document.t是undefind。

 此时可以看到document.cookie已经被我们的img标签覆盖了

 

 从上面可以看出通过id属性,替换掉了本身appendChild函数的属性,导致appendChild成为le <img id="appendChild">,成功将其给覆盖掉了。也就是说通过多层覆盖掉了appendChild方法。

攻击方法

既然我们可以通过这种方式去创建或者覆盖document或者window对象的某些值,但是看起来我们举的例子只是利用标签创建或者覆盖最终得到的也是标签,是一个HTMLElement对象。

此时我们这里转换成了一个对象,但是对于大多数情况来说,我们可能更需要将其转换为一个可控的字符串类型,以便我们进行操作。

    Object.getOwnPropertyNames(window)
        .filter(p => p.match(/Element/))
        .map(p => window[p])
        .filter(p => p && p.prototype && prototype.toString
            !== Object.prototype.toString)

 我们可以得到两种标签对象:

HTMLAreaElement(<area>)& HTMLAnchorElement(<a>),这两个标签对象我们都可以利用href属性来进行字符串转换。

此时通过a标签,就可以将href中的内容转换为字符串。此时解决了覆盖后的到的不是一个对象,而是字符串。

 

但是如果我们需要的是x.y这种形式呢?两层结构我们应该怎么办呢?我们可以尝试上述的办法:

<div id=x>
    <a id=y href='1:hasaki'></a>
</div>
<script>
    aler(x.y);
</script>

 这里无论第一个标签怎么组合,得到的结果都只是undefined。但是我们可以通过另一种方法引入name属性就会有其他的效果。

HTMLCollection是一个element的“集合”类,让我们值得注意的是我们可以通过collection[name]的形式来调用其中的元素,所以我们似乎可以通过先构建一个HTMLCollection,再通过collection[name]的形式来调用。通俗来说也就是通过集合的方式来调用。

<div id="x">
    <a id="x" name=y href="1:hasaki"></a>
</div>

首先我们可以先来看下id=x的时候

 可以将其打印出来看看到底是什么?

可以看出是一个集合,而集合有什么特征呢?

是集合就意味着可以循环,可以取到里面的值。

 此时我们通过自定义标签,使用两层结构,就可以将我们的toString方法中的字符取出来。

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

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

相关文章

针对博客系统的自动化测试

针对博客系统进行web自动化测试 文章目录针对博客系统进行web自动化测试引入依赖创建出合适的目录结构AutoTestUtilsBlogLoginTestBlogListTestBLogEditTestBlogDetailTest使用套件执行关于博客系统的自动化测试的源代码已经上传至gitee链接 引入依赖 首先在pom.xml上导入依赖…

【错误记录】映射文件发生改变

遇到映射文件发生改变在项目中有的地方是使用反射机制进行的操作&#xff0c;可能对于类进行了某些更改&#xff0c;科室映射文件没有更改&#xff0c;采用匿名就会出现这样的问题&#xff0c;解决办法要么更改映射文件&#xff0c;要不不要使用匿名调用。JavaDocs路径报红Clas…

ruoyi-vue-plus学习2(异步日志)(@EventListener)(@Async)(线程池池相关)

ruoyi-vue-plus的日志打印是通过监听器实现的&#xff0c;和原版若依稍稍不同 找到登录时记录日志的方法 该方法如下 这里的参数LogininforEvent为消息类&#xff0c;注意&#xff0c;貌似高版本的spring定义消息类不需要继承ApplicationEvent类也行。直接使用普通实体类就行…

详解-序列化和反序列化

文章目录问题使用场景序列化协议TCP/IP 4 层模型的哪一层&#xff1f;序列化协议问题 什么是序列化和反序列化? Java对象保存在文件中/网络传输Java对象&#xff0c;这些场景需要用到序列化。 序列化&#xff1a;将Java对象或数据结构转化成二进制流反序列化&#xff1a;将二…

Git简单使用

Git简单使用 git初始化操作 pwd&#xff1a;显示当前目录 git init&#xff1a;把这个目录变成git可以管理的仓库 git clone&#xff1a; 从现有 Git 仓库中拷贝项目&#xff08;类似 svn checkout&#xff09;。 克隆仓库的命令格式为&#xff1a; git clone <repo&g…

【Java基础 下】 031 -- 反射 动态代理

一、什么是反射&#xff1f; 换句话说就是&#xff08;从类里拿出来&#xff09; 可以获取到&#xff1a;&#xff08;利用反射&#xff0c;我们可以获取到类中所有的东西&#xff09; 获取是先从class字节码文件中获取的 二、获取class对象的三种方式 三种方式也对应了三种阶段…

Unity之Addressable使用注意事项

基本设置 Profile文件中配置资源构建路径和资源首次加载路径&#xff0c;资源如何设置了缓存&#xff0c;在首次加载之后会将再用缓存在缓存目录&#xff0c;后面将直接从缓存目录中读取&#xff0c;RemoteLoadPath一般要设置成可以动态修改类型的参数&#xff0c;方便项目发包…

[oeasy]python0104_指示灯_显示_LED_辉光管_霓虹灯

编码进化 回忆上次内容 x86、arm、riscv等基础架构 都是二进制的包括各种数据、指令 但是我们接触到的东西 都是屏幕显示出来的字符 计算机 显示出来的 一个个具体的字型 计算机中用来展示的字型 究竟是 如何进化的 呢&#xff1f;&#x1f914;&#x1f914; 模拟电路时…

在线客服机器人是什么?有什么用处?

在线客服机器人是客服自动智能问答 真人模拟应答以及意向分类 高效低成本可以同时进行一对多服务&#xff0c;而在一些情况下&#xff0c;在线客服机器人还可以进行客户接待&#xff0c;完全代替人工客服&#xff0c;提高整个客服中心的接待量。在这里我们拿ttkefu在线机器人为…

Spring学习——Maven进阶

分模块开发与设计 创建模块 书写模块代码 通过maven指令安装模块到本地仓库(install指令) 在pom.xml中导入坐标执行maven的install命令将模块安装到本地maven仓库 团队内部开发可以发布模块功能到团队内部可共享的仓库中&#xff08;私服) 依赖管理 依赖指当前项目运行所需…

JAVA SE:多线程

一、线程简介多任务&#xff0c;看似同时在做&#xff0c;实际上同一时间只做一件事多线程&#xff0c;相当于路上多加一条车道普通方法vs多线程&#xff1a; 程序、进程、线程&#xff1a;程序&#xff1a;静态的概念&#xff0c;程序和数据的有序集合进程&#xff1a;操作系统…

CleanMyMac X4.20最新Mac系统垃圾清理工具

CleanMyMac X是一款Mac系统垃圾清理工具,可以清除Mac系统多余的语言包、系统缓存、应用程序、PowerPc软件运行库等,是硬盘瘦身的好工具。在面对一款多功能型的软件时&#xff0c;复杂的操作面板是最容易让人头疼的&#xff0c;好在 CleanMyMac 一直以来都原生支持简体中文语言&…

python第五天作业~基础练习

目录 求十进制数字9的二进制编码&#xff1a; 求十进制数字9的二进制编码中的1的数量&#xff1a; 作业15&#xff1a;求1~100之间不能被3整除的数之和 作业16&#xff1a;给定一个正整数N,找出1到N&#xff08;含&#xff09;之间所有质数的总和 作业19&#xff1a;计算PI&…

为什么那么多人转行编程都首选Java

计算机专业通常从C语言开始学&#xff0c;非计算机专业想要转行编程首选Java的主要原因&#xff1a;市场需求量大。Java可以用于开发各种应用程序&#xff0c;包括企业级Web应用程序、移动应用程序、桌面应用程序、游戏、嵌入式系统、云基础设施等等。在游戏领域也有两大经典游…

Meta带头甩卖 VR头显打起价格战

新春三月&#xff0c;准备入手VR头显的“等等党”终于迎来降价利好。以Meta为首的一众VR厂商们纷纷打折&#xff0c;无论是为了清理库存、回收成本还是让步硬件新品&#xff0c;普通消费者都喜闻乐见。 上周五&#xff0c;Meta 率先官宣Meta Quest Pro与Meta Quest 2 的256GB版…

效率工具-快速创建虚拟机,Vagrant真香!

工欲善其事&#xff0c;必先利其器&#xff0c;开发环境和开发工具就是我们开发人员的剑&#xff0c;所以我们需要一个快并且好用的剑。本文是向大家介绍虚拟机创建vagrant&#xff0c;它能够实现开发人员本机快速创建虚拟机&#xff0c;能够带来高效学习各种技术价值。1 Vagra…

我的投稿之旅

一、铁道科学与工程学报选择这个期刊的原因是&#xff1a;感觉影响因子较低&#xff0c;而且实验室有师兄师姐中过这个期刊&#xff0c;所以抱着试一试的心态投了。投稿之前需要去官网注册账号由于方向不一致&#xff0c;被退稿了“您的稿件内容不属于本刊刊载范畴&#xff0c;…

Content-Type (MIME) el-upload文件、图片上传 | 文件改名 | 大文件 | 文件下载

MIME 为数据格式标签&#xff1b;最初 MIME 是用于电子邮件系统的&#xff0c;后来 HTTP 也采用了这一方案。 在HTTP协议消息头中&#xff0c;使用Content-Type来表示请求和响应中的媒体类型信息。 Content-Type&#xff1a;type/subtype ;parametertype 主类型&#xff0c;任…

【LeetCode每日一题】——135.分发糖果

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 贪心算法 二【题目难度】 困难 三【题目编号】 135.分发糖果 四【题目描述】 n 个孩子站成一…

【论文研读】无人机飞行模拟仿真平台设计

无人机飞行模拟仿真平台设计 摘要&#xff1a; 为提高飞行控制算法的研发效率,降低研发成本,基于数字孪生技术设计一个无人机硬件在环飞行模拟仿真平台。从几何、物理和行为3个方面研究无人机数字模型构建方法,将物理实体以数字化方式呈现。设计一种多元融合场景建模法,依据属…