HTTP严格安全传输(HTTP Strict Transport Security, HSTS)chromuim实现源码分析(二)

HTTP strict transport security (HSTS) is defined in
http://tools.ietf.org/html/ietf-websec-strict-transport-sec

HTTP-based dynamic public key pinning (HPKP) is defined in
http://tools.ietf.org/html/ietf-websec-key-pinning.

深入理解需参考chromuim设计文档:

https://www.chromium.org/developers/design-documents

(中文版)https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//General_Architecture/Threading.html

chromuim在线源码:https://chromium.googlesource.com/chromium/src/+/58.0.3025.2/

------------------------------------------------分割线---------------------------------------------------------------------------------

通过观察文件名,发现涉及的文件主要是net目录下的:

Transport_security_persister.cc

Transport_security_persister.h

Transport_security_state.cc

Transport_security_state.h

Url_request_http_job.cc

在开始之前,需要从源码层面了解chromuim的网络栈部分,可参考《WebKit技术内幕》作者朱永盛的博文http://blog.csdn.net/milado_nju/article/details/9255563以及官网网络栈部分的文档http://www.chromium.org/developers/design-documents/network-stack。简单来说,发送网络请求基本都是通过URLRequest类,再根据不同协议选择不同的工厂,如HTTP为URLRequestHttpJob,由于HSTS针对HTTP,所以也只需关注URLRequestHttpJob;另一个重要的类是URLRequestContext,它包含其他完成URL请求的上下文信息,如cookie、主机解析、缓存以及HSTS信息等;许多URLRequest对象共享一个URLRequestContext。本文采用好理解的“自上而下”顺序来进行总结分析,但是实际中由于作者对chromuim完全没有认识,其实是从源码中搜索关键词看注释再查找引用一步一步摸索的。

类URLRequestHttpJob的Factory方法在创建实例前,调用了Url_request_http_job.cc中的MaybeInternallyRedirect()函数对是否要进行升级HSTS进行了判断,通过名字可以猜出chromuim是采用内部重定向的方式来实现HTTP到HTTPS升级的,这跟使用开发者工具调试时观察到的请求一致。

MaybeInternallyRedirect函数中,根据request->url()来判断该域名是否应该升级HSTS,若hsts->ShouldUpgradeToSSL返回false,就返回nullptr,不用重定向;否则,使用Replacements类,根据request->url()的协议,来进行替换为https或wss(即websocket),再返回307状态码的URLRequestRedirectJob类。因此,URLRequestHttpJob返回URLRequestRedirectJob而非正常的URLRequestHttpJob,进行重定向来升级协议。由此可见,hsts的ShouldUpgradeToSSL方法就至关重要了,是否升级https全看它的返回值了。

hsts是从request->context()->transport_security_state()取出,通过查看源码,即是URLRequest中的URLRequestContext指针类型变量context_中的TransportSecurityState*类型的transport_security_state变量。这句话有点绕,其实就是从URLRequestContext取出了保存HSTS信息的变量,该变量(260行)类型为TransportSecurityState*,TransportSecurityState就是实现HSTS机制的重点了。另外,源码指针的实现也呼应了开始提到的多URLRequest对一个URLRequestContext。

Transport_security_state.h中可以看到TransportSecurityState的定义,它驻留在内存中追踪哪个域名启用了HSTS和PKP,并且用SetDelegate方法注册了一个代理来存储状态到硬盘中。为了解一个类,发现从其对应的单元测试文件可以得到一个整体的感性认识,单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。从单元测试文件Transport_security_state_unittest.cc中可以看出该类的简单用法,以及作者已经考虑到的一些避免HSTS被绕过的情况。比如这里就考虑了域名末尾加“.”和不加“.”在本策略中是一样的。并且,除了subdomain选项和preload外, 还有一些极端情况,如下所述。(Google C++ Testing Framework可参考这个网址http://developer.51cto.com/art/201108/285290.htm)

(1)防止拒绝服务的产生(比如添加一个域名为“.”的情况),

(2)对大小写不敏感(Google.com与GooGLe.CoM是一样的),

(3)规则冲突覆盖以更具体的为准(example.com的subdomain为true,而foo.example.com的subdomain为false,那么sub.foo.example.com的HSTS情况应为false;这点浏览器的实现情况应引起网站管理员的注意,可测试网站有没有这样的漏洞,RFC标准里我不记得提到),

(4)若设置了subdomain,不规范子域名也应使用HSTS(如2\x01.foo.example.test返回true),

(5)域名删除的情况。

下面的这个810行的测试没看懂,为何exampl1.com一会儿期望为true,一会儿为false?猜想可能是在测试单元测试功能有没有正常工作吧。

--------------------------------------------------分割线-------------------------------------------------

由单元测试和之前的源码分析以及源码中的注释可以得到,TransportSecurityState的ShouldUpgradeToSSL是判断该URL是否应该升级HTTPS的方法。查看ShouldUpgradeToSSL方法,发现是根据STSState类型变量的ShouldUpgradeToSSL()来返回布尔值的,从名称看依次检查了动态HSTS和预置的HSTS(即preload,如www.google.com已经预置在浏览器里了)。

先看类STSState,他是一个内部类,实现非常简单,注释说明它描述了HSTS的状态,属性包括过期时间、include_subdomains标识、域名等,根据host来由GetDynamicSTSState和GetStaticDomainState更新。首先来看GetDynamicSTSState,该函数根据host查询保存的信息,来对result赋值,并且特别强调了以更具体的结果为准;通过源码,其进行了host的规范化,然后用迭代器和enabled_sts_hosts_以及hash后的host(其实本地存储的HSTS信息文件中域名只有哈希后的值,为了隐私)来进行查询,并判断了查询结果中当前时间是否超过了过期时间,对结果result进行赋值为j->second,这样STSState就有值可以判断ShouldUpgradeToSSL了。ShouldUpgradeToSSL方法仅仅是比较upgrade_mode属性是否为MODE_FORCE_HTTPS。由此可见,STSStateMap类型的enabled_sts_hosts_就是本地维护HSTS信息的变量了,由此将我们引向了STSStateMap类。其实,若要发现实现细节方面的漏洞就需要详细看实现查找的算法了(CanonicalizeHost),对发现逻辑漏洞帮助不大,没有耐心的我就先略过算法细节分析。

bool TransportSecurityState::GetDynamicSTSState(const std::string& host,
STSState* result) {
DCHECK(CalledOnValidThread()); const std::string canonicalized_host = CanonicalizeHost(host);
if (canonicalized_host.empty())
return false; base::Time current_time(base::Time::Now()); for (size_t i = ; canonicalized_host[i]; i += canonicalized_host[i] + ) {
std::string host_sub_chunk(&canonicalized_host[i],
canonicalized_host.size() - i);
STSStateMap::iterator j = enabled_sts_hosts_.find(HashHost(host_sub_chunk));
if (j == enabled_sts_hosts_.end())
continue; // If the entry is invalid, drop it.
if (current_time > j->second.expiry) {
enabled_sts_hosts_.erase(j);
DirtyNotify();
continue;
} // If this is the most specific STS match, add it to the result. Note: a STS
// entry at a more specific domain overrides a less specific domain whether
// or not |include_subdomains| is set.
if (current_time <= j->second.expiry) {
if (i == || j->second.include_subdomains) {
*result = j->second;
result->domain = DNSDomainToString(host_sub_chunk);
return true;
} break;
}
} return false;
}

STSStateMap并不神秘,其实就是map<std::string, STSState>。那么具体是哪些函数对这个信息进行维护呢,通过对enabled_sts_hosts_查找引用,共12处,分布在EnableSTSHost、DeleteDynamicDataForHost、ClearDynamicData、DeleteAllDynamicDataSince、AddOrUpdateEnabledSTSHosts。

EnableSTSHost首先对host进行规范化,然后根据state.ShouldUpgradeToSSL()是否应该升级https来决定是存入信息还是删除信息,存入的时候可以看到,保存的不是host,而是哈希后的host;该方法中已经使用了ShouldUpgradeToSSL,可见state内已经有信息,这一步只是进行存储的操作,还需要查看引用是谁调用EnableSTSHost传入了state。通过查找引用,发现是AddHSTSInternal方法,决定ShouldUpgradeToSSL返回值的upgrade_mode来自于该方法的参数,还需要继续寻找调用者。(感觉这俩方法合并效率会更高些)AddHSTSHeader根据解析HSTS头的ParseHSTSHeader函数返回的max_age变量InSeconds()==0来判断是否应该强制升级HTTPS,这种时间等于0的做法容易产生问题,负数、溢出是否影响?判断有无这种漏洞就再次需要详细看实现算法细节了。

URLRequestHttpJob::ProcessStrictTransportSecurityHeader调用了AddHSTSHeader,实现中可以看到,浏览器仅接受HTTPS且没有证书错误的HSTS,从而避免恶意服务器拒绝服务其他域名;域名是IP地址也不会接受HSTS;有多个HSTS头则只接受第一个。注意到传入AddHSTSHeader作为host参数的是request_info_.url.host(),而这些变量不是通过参数传入而是类自身的变量,所以没必要再继续向上追。(当然缺乏经验的我肯定会犯错,向上追了好几步。ProcessStrictTransportSecurityHeader()被URLRequestHttpJob::NotifyHeadersComplete()调用,而调用NotifyHeadersComplete()的地方查找调用看似很多,好像处理头部时都会调用,只要注意限制在URLRequestHttpJob类,就发现其实只有一个调用者,SaveCookiesAndNotifyHeadersComplete,再网上追查两步就到了URLRequestHttpJob类的回调机制。但由于是检查HSTS头部时检查的ssl证书等信息,并且不存在其他路径到添加添加HSTS信息的函数,所以攻击者也难以通过别的通道来拒绝服务其他域名;本文只关心HSTS带来的问题,故没有必要再向上追寻,要寻找拒绝服务攻击可能性,关键就在于传入AddHSTSHeader的值了)

如果这个request_info_.url变量和ssl_info变量出问题:规范化后域名canonicalized_host哈希值碰撞(采用的SHA256几乎不可破)、和当前域名对应错误(若这个有问题那么cookie什么的就可以全乱套了,感觉出问题几率应该不大)、规范化后的域名和其他域名能一致(好多数据结构要看);则攻击者就可以操作其他域名HSTS信息。URLRequestHttpJob中request_info_.url是URLRequestHttpJob::Start()中赋值request_info_.url = request_->url();request_继承自父类URLRequestJob,指向了创建这个job的URLRequest。

到此为止,我们对HSTS实现的基本流程有了一定了解,但这仅仅是对管理enabled_sts_hosts_的EnableSTSHost函数追踪所学习到的,还有DeleteDynamicDataForHost、ClearDynamicData、DeleteAllDynamicDataSince、AddOrUpdateEnabledSTSHosts还有待继续学习。最后总结出如下两个图:

By Ascii0x03

转载请注明出处:http://www.cnblogs.com/ascii0x03/p/6375825.html

HTTP严格安全传输(HTTP Strict Transport Security, HSTS)chromuim实现源码分析(一)的更多相关文章

  1. HTTP严格安全传输(HTTP Strict Transport Security, HSTS)chromuim实现源码分析(二)

    HTTP严格安全传输(HTTP Strict Transport Security, HSTS)chromuim实现源码分析(一) 下面来查看其他对保存HSTS信息的enabled_sts_hosts ...

  2. Nginx-HTTP Strict Transport Security(HSTS)

    HTTP Strict Transport Security(HSTS) HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过HTTP ...

  3. HTTP Strict Transport Security (HSTS) in ASP.NET Core

    本文是<9012年了,还不会Https>的后篇,本文着重聊一聊 HTTP Strict Transport Security协议的概念和应用. 启用HTTPS还不够安全 站点通过HTTPS ...

  4. Spring Security(四) —— 核心过滤器源码分析

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...

  5. HTTP Strict Transport Security实战详解

    HTTP Strict Transport Security (通常简称为HSTS) 是一个安全功能,它告诉浏览器只能通过HTTPS访问当前资源, 禁止HTTP方式. 0×01. Freebuf百科: ...

  6. nginx配置Strict Transport Security

    一个网站接受一个HTTP的请求,然后跳转到HTTPS,用户可能在开始跳转前,通过没有加密的方式和服务器对话,比如,用户输入http://zt.test.com或者直接zt.test.com.这样存在中 ...

  7. HTTP Strict Transport Security (通常简称为HSTS)

    HTTP Strict Transport Security (通常简称为HSTS) 是一个安全功能,它告诉浏览器只能通过HTTPS访问当前资源, 禁止HTTP方式. Freebuf百科:什么是Str ...

  8. HTTP Strict Transport Security

    HTTP Strict Transport Security (通常简称为HSTS) 是一个安全功能,它告诉浏览器只能通过HTTPS访问当前资源, 禁止HTTP方式. 作用 一个网站接受一个HTTP的 ...

  9. Spring Security(3):配置与自动配置的介绍及源码分析

    基于注解的配置(Java Configuration)从Spring Security 3.2开始就已经支持,本篇基于Spring boot注解的配置进行讲解,如果需要基于XML配置(Security ...

随机推荐

  1. (简单) POJ 1511 Invitation Cards,SPFA。

    Description In the age of television, not many people attend theater performances. Antique Comedians ...

  2. kvm下Windows激活方式小计

    使用kvm创建widnwos镜像模板,镜像模板默认是已经激活的正版系统,但是使用程序拷贝部署到不同的机器后发现已经激活的系统变成未激活状态,我们需求就是需要拷贝到不同的机器也能显示是正版系统 网上找了 ...

  3. Cocos2d-x 的“HelloWorld” 深入分析

    本节所用Cocos2d-x版本:cocos2d-1.0.1-x-0.12.0 不能免俗,一切都从“HelloWorld!”开始.打开HelloWorld工程,里面有两个文件目录Classes和win3 ...

  4. SQLSERVER TRUE、FALSE、UNKNOWN

    null和其他值比较都是unknown 在SQL中逻辑表达式的可能值包括TRUE.FALSE.UNKNOWN.他们被称为三值逻辑.三值逻辑是SQL所特有的.大多数的变成语言的逻辑表达式 只有TRUE或 ...

  5. PHP利用数组构造JSON

    问题起因 以往都是直接用构造数组的形式构造json 例子: $arr = array("A"=>"1","B"=>"2 ...

  6. UVa 10718 - Bit Mask

    题目大意:给一数N,在区间[L, U]上找到一个数M使得M| N的值最大,如果有M有多个可能值,取最小的那个值. 从最高位开始逐位判断,如果N的该位为0,为使M | N的值最大,M的该位应考虑置为1, ...

  7. 除trigger()方法外的jquery手动触发事件

    trigger()可以触发指定事件是大家基本都知道的了. 除了trigger()之外我们也可以这样来触发: html <div id="box" style="he ...

  8. 判断js对象的数据类型,有没有一个最完美的方法?

    先来一个例子: var string1=""; var string2=new String(""); alert(typeof string1); // st ...

  9. METRO风格

    METRO风格是指微软在WINDOWS PHONE 7中新加入的界面风格,并且计划将其用于windows8中的开始菜单界面.该界面的特点是简洁高效,每一个METRO图标都没有边框,形状有点像地铁站中的 ...

  10. C语言strstr()函数:返回字符串中首次出现子串的地址

    今天又学到了一个函数 头文件:#include <string.h> strstr()函数用来检索子串在字符串中首次出现的位置,其原型为:    char *strstr( char *s ...