你不知道的 HTTPS中间人攻击

前言

研究生毕业了,好好给自己放了个假期,休息了两周,文章博客都没有更新。从大学开始基本上没过暑假,匆匆忙忙的。再过两天,就要去腾讯工作了,做了自己喜欢的网络安全,重新成为小学弟,又是一场崭新的旅行。废话不多说,开始今天的主题。

在上一篇文章BaseProxy:异步http/https代理中,我介绍了自己的开源项目BaseProxy,这个项目的初衷其实是为了渗透测试,抓包改包。在知识星球中,有很多朋友问我这个项目的原理及实现代码,本篇文章就讲解一下和这个项目相关的HTTPS的中间人攻击。

HTTPS隧道代理

HTTPS隧道代理简单来说是基于TCP协议数据透明转发,在RFC中,为这类代理给出了规范,Tunneling TCP based protocols through Web proxy servers。浏览器客户端发送的原始 TCP 流量,代理发送给远端服务器后,将接收到的 TCP 流量原封不动返回给浏览器。交互流程如下图所示:

以连接百度为例,浏览器首先发起 CONNECT 请求:

CONNECT baidu.com:443 HTTP/1.1

代理收到这样的请求后,根据 host 地址与服务器建立 TCP 连接,并返回给浏览器连接成功的HTTP 报文(没有报文体):

HTTP/1.1 200 Connection Established

浏览器一旦收到这个响应报文,就可认为与服务器的 TCP 连接已打通,后续可直接透传。

在BaseProxy项目中, https=False是对于https实行透传。

HTTPS中间人攻击

HTTPS 代理本质上是隧道透传,仅仅是转发 TCP 流量,无法获取其中的GET/POST请求的具体内容。这就很麻烦,现在 HTTPS 越来越普遍,做安全测试也就拿不到 HTTP 请求。那怎么做呢? 代理需要对 TCP 流量进行解密,然后对明文的HTTP请求进行分析,这样的代理就称为HTTPS中间人。

正常的HTTPS隧道

在上图中,隧道代理负责浏览器和服务器之间的TCP流量的转发。

HTTPS中间人

如果需要对TCP流量进行分析和修改,就要将上图中的代理功能一分为二,即代理既要当做TLS服务端,又要当做TLS客户端,如下图所示。

在上图中,用一个 TLS 服务器伪装成远端的真正的服务器,接收浏览器的 TLS 流量,解析成明文。这个时候可以对明文进行分析修改,然后用明文作为原始数据,模拟 TLS 客户端将原始数据向远端服务器转发。

CA证书问题

CA证书是我当时遇到的坑,之前没接触过。HTTPS传输是需要证书的,用来对HTTP明文请求进行加解密。一般正常网站的证书都是由合法的 CA 签发,则称为合法证书。在上图中,浏览器会验证隧道代理中 TLS 服务器 的证书:

  1. 验证是否是合法 CA 签发。
  2. 验证该证书 CN 属性是否是所请求的域名。即若浏览器打开 www.baidu.com,则返回的证书 CN 属性必须是 www.baidu.com

对于第一点,合法的 CA 机构不会给我们签发证书的,否则HTTPS安全性形同虚设,因此我们需要自制CA证书,并导入到浏览器的信任区中。

对于第二点,我们由于需要对各个网站进行HTTPS拦截,因此我们需要实时生成相应域名的服务器证书,并使用自制的CA证书进行签名。

BaseProxy源码分析

通过以上的讲解,HTTPS中间人的原理已经基本清楚,下面简要地说明一下BaseProxy源码。

HTTP服务器

代理其实就是一个HTTPS服务器,使用了Python中的HTTPServer类,为了增加异步特性,将其放到线程池中。

class MitmProxy(HTTPServer):

    def __init__(self,server_addr=('', 8788),RequestHandlerClass=ProxyHandle, bind_and_activate=True,https=True):
        HTTPServer.__init__(self,server_addr,RequestHandlerClass,bind_and_activate)
        logging.info('HTTPServer is running at address( %s , %d )......'%(server_addr[0],server_addr[1]))
        self.req_plugs = []##请求拦截插件列表
        self.rsp_plugs = []##响应拦截插件列表
        self.ca = CAAuth(ca_file = "ca.pem", cert_file = 'ca.crt')
        self.https = https

    def register(self,intercept_plug):

        if not issubclass(intercept_plug, InterceptPlug):
            raise Exception('Expected type InterceptPlug got %s instead' % type(intercept_plug))

        if issubclass(intercept_plug,ReqIntercept):
            self.req_plugs.append(intercept_plug)

        if issubclass(intercept_plug,RspIntercept):
            self.rsp_plugs.append(intercept_plug)

class AsyncMitmProxy(ThreadingMixIn,MitmProxy):

    pass

HTTPS请求与响应

对HTTP请求的解析与响应,关键在于ProxyHandle类,实现其中的doCONNECT和doGET方法,并在do_CONNECT方法中判断是使用透传模式还是中间人模式。

class ProxyHandle(BaseHTTPRequestHandler):

    def __init__(self,request,client_addr,server):
        self.is_connected = False
        BaseHTTPRequestHandler.__init__(self,request,client_addr,server)

    def do_CONNECT(self):
        '''
        处理https连接请求
        :return:
        '''
        self.is_connected = True#用来标识是否之前经历过CONNECT
        if self.server.https:
            self.connect_intercept()
        else:
            self.connect_relay()

    def do_GET(self):
        '''
        处理GET请求
        :return:
        '''
      ......

    do_HEAD = do_GET
    do_POST = do_GET
    do_PUT = do_GET
    do_DELETE = do_GET
    do_OPTIONS = do_GET

CA证书生成以及代理证书的自签名

与CA证书相关的内容都放在了CAAuth类中。生成CA证书代码如下:

def _gen_ca(self,again=False):
        # Generate key
        #如果证书存在而且不是强制生成,直接返回证书信息
        if os.path.exists(self.ca_file_path) and os.path.exists(self.cert_file_path) and not again:
            self._read_ca(self.ca_file_path) #读取证书信息
            return
        self.key = PKey()
        self.key.generate_key(TYPE_RSA, 2048)
        # Generate certificate
        self.cert = X509()
        self.cert.set_version(2)
        self.cert.set_serial_number(1)
        self.cert.get_subject().CN = 'baseproxy'
        self.cert.gmtime_adj_notBefore(0)
        self.cert.gmtime_adj_notAfter(315360000)
        self.cert.set_issuer(self.cert.get_subject())
        self.cert.set_pubkey(self.key)
        self.cert.add_extensions([
            X509Extension(b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
            X509Extension(b"keyUsage", True, b"keyCertSign, cRLSign"),
            X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=self.cert),
        ])
        self.cert.sign(self.key, "sha256")
        with open(self.ca_file_path, 'wb+') as f:
            f.write(dump_privatekey(FILETYPE_PEM, self.key))
            f.write(dump_certificate(FILETYPE_PEM, self.cert))

        with open(self.cert_file_path, 'wb+') as f:
            f.write(dump_certificate(FILETYPE_PEM, self.cert))

根据域名实时生成服务器证书,并对服务器证书进行自签名。代码如下:

def _sign_ca(self,cn,cnp):
        #使用合法的CA证书为代理程序生成服务器证书
        # create certificate
        try:

            key = PKey()
            key.generate_key(TYPE_RSA, 2048)

            # Generate CSR
            req = X509Req()
            req.get_subject().CN = cn
            req.set_pubkey(key)
            req.sign(key, 'sha256')

            # Sign CSR
            cert = X509()
            cert.set_version(2)
            cert.set_subject(req.get_subject())
            cert.set_serial_number(self.serial)
            cert.gmtime_adj_notBefore(0)
            cert.gmtime_adj_notAfter(31536000)
            cert.set_issuer(self.cert.get_subject())
            ss = ("DNS:%s" % cn).encode(encoding="utf-8")

            cert.add_extensions(
                [X509Extension(b"subjectAltName", False, ss)])

            cert.set_pubkey(req.get_pubkey())
            cert.sign(self.key, 'sha256')

            with open(cnp, 'wb+') as f:
                f.write(dump_privatekey(FILETYPE_PEM, key))
                f.write(dump_certificate(FILETYPE_PEM, cert))
        except Exception as e:
            raise Exception("generate CA fail:{}".format(str(e)))

原文发布于微信公众号 - 七夜安全博客(qiye_safe)

原文发表时间:2018-07-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Web 开发

申请SSL开启SPDY服务

12月10日的时候,整个站点就已经装上了Nginx 1.4,配置文件里面已经可以开启SPDY功能,不过那时候一直缺少一枚SSL证书。

810
来自专栏阮一峰的网络日志

JSON Web Token 入门教程

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,本文介绍它的原理和用法。

2065
来自专栏QQ音乐技术团队的专栏

分析 Android V2 新签名打包机制

本文实现了一种在 apk 的签名块中写入信息,读取信息,删除信息还原 apk 等功能,验证了在签名块中写入信息可以通过 v2 检验的例子。

2.5K0
来自专栏mantou大数据

深入浅出JWT(JSON Web Token )

JSON Web Token(JWT)是一个开放式标准(RFC 7519),它定义了一种紧凑(Compact)且自包含(Self-contained)的方式,用...

63511
来自专栏FreeBuf

直面冥王:最新爆发的C#敲诈木马HadesLocker解读

近日哈勃分析系统捕获到一类由C#语言编写的新的敲诈勒索木马。之前出现 的C#语言编写的木马只是简单地调用了一些C#库来辅助开发。与之相比,这次的变种增加了多层嵌...

2906
来自专栏安恒网络空间安全讲武堂

WriteUp分享 | LCTF的一道padding oracle攻击+sprintf格式化字符串导致的SQL注入

0x00题目 http://111.231.111.54/ 泄露了两个源码 .login.php.swp .admin.php.swp 源码丢在最下面,可用vi...

2338
来自专栏网络

突破封闭 Web 系统的技巧之正面冲锋

在互联网安全服务公司乙方工作的人或者进行 SRC 众测等相关渗透测试时,经常碰到客户只给一个 "xxx信息管理系统"、"xxx平台"之类的一个 Web 登录界面...

25510
来自专栏FreeBuf

一个纯JS脚本的文档敲诈者剖析(附解密工具)

0x00 概述 近日,腾讯反病毒实验室拦截到一个名为RAA的敲诈者木马,其所有的功能均在JS脚本里完成。这有别于过往敲诈者仅把JS脚本当作一个下载器,去下载和执...

4777
来自专栏小筱月

https 加密、http2.0、keep-alive

HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏...

1460
来自专栏码神联盟

短信 | 教你使用 JAVA实现 【短信发送】 功能

5394

扫码关注云+社区

领取腾讯云代金券