你不知道的 HTTPS中间人攻击

Posted 七夜安全博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你不知道的 HTTPS中间人攻击相关的知识,希望对你有一定的参考价值。

前言

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

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

HTTPS隧道代理

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

你不知道的 HTTPS中间人攻击

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

 
   
   
 
  1. CONNECT baidu.com:443 HTTP/1.1

 
   
   
 
  1. HTTP/1.1 200 Connection Established

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

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

HTTPS中间人攻击

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

正常的HTTPS隧道

你不知道的 HTTPS中间人攻击

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

HTTPS中间人

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

你不知道的 HTTPS中间人攻击

在上图中,用一个 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类,为了增加异步特性,将其放到线程池中。

 
   
   
 
  1. class MitmProxy(HTTPServer):

  2.    def __init__(self,server_addr=('', 8788),RequestHandlerClass=ProxyHandle, bind_and_activate=True,https=True):

  3.        HTTPServer.__init__(self,server_addr,RequestHandlerClass,bind_and_activate)

  4.        logging.info('HTTPServer is running at address( %s , %d )......'%(server_addr[0],server_addr[1]))

  5.        self.req_plugs = []##请求拦截插件列表

  6.        self.rsp_plugs = []##响应拦截插件列表

  7.        self.ca = CAAuth(ca_file = "ca.pem", cert_file = 'ca.crt')

  8.        self.https = https

  9.    def register(self,intercept_plug):

  10.        if not issubclass(intercept_plug, InterceptPlug):

  11.            raise Exception('Expected type InterceptPlug got %s instead' % type(intercept_plug))

  12.        if issubclass(intercept_plug,ReqIntercept):

  13.            self.req_plugs.append(intercept_plug)

  14.        if issubclass(intercept_plug,RspIntercept):

  15.            self.rsp_plugs.append(intercept_plug)

  16. class AsyncMitmProxy(ThreadingMixIn,MitmProxy):

  17.    pass

HTTPS请求与响应

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

 
   
   
 
  1. class ProxyHandle(BaseHTTPRequestHandler):

  2.    def __init__(self,request,client_addr,server):

  3.        self.is_connected = False

  4.        BaseHTTPRequestHandler.__init__(self,request,client_addr,server)

  5.    def do_CONNECT(self):

  6.        '''

  7.        处理https连接请求

  8.        :return:

  9.        '''

  10.        self.is_connected = True#用来标识是否之前经历过CONNECT

  11.        if self.server.https:

  12.            self.connect_intercept()

  13.        else:

  14.            self.connect_relay()

  15.    def do_GET(self):

  16.        '''

  17.        处理GET请求

  18.        :return:

  19.        '''

  20.      ......

  21.    do_HEAD = do_GET

  22.    do_POST = do_GET

  23.    do_PUT = do_GET

  24.    do_DELETE = do_GET

  25.    do_OPTIONS = do_GET

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

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

 
   
   
 
  1. def _gen_ca(self,again=False):

  2.        # Generate key

  3.        #如果证书存在而且不是强制生成,直接返回证书信息

  4.        if os.path.exists(self.ca_file_path) and os.path.exists(self.cert_file_path) and not again:

  5.            self._read_ca(self.ca_file_path) #读取证书信息

  6.            return

  7.        self.key = PKey()

  8.        self.key.generate_key(TYPE_RSA, 2048)

  9.        # Generate certificate

  10.        self.cert = X509()

  11.        self.cert.set_version(2)

  12.        self.cert.set_serial_number(1)

  13.        self.cert.get_subject().CN = 'baseproxy'

  14.        self.cert.gmtime_adj_notBefore(0)

  15.        self.cert.gmtime_adj_notAfter(315360000)

  16.        self.cert.set_issuer(self.cert.get_subject())

  17.        self.cert.set_pubkey(self.key)

  18.        self.cert.add_extensions([

  19.            X509Extension(b"basicConstraints", True, b"CA:TRUE, pathlen:0"),

  20.            X509Extension(b"keyUsage", True, b"keyCertSign, cRLSign"),

  21.            X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=self.cert),

  22.        ])

  23.        self.cert.sign(self.key, "sha256")

  24.        with open(self.ca_file_path, 'wb+') as f:

  25.            f.write(dump_privatekey(FILETYPE_PEM, self.key))

  26.            f.write(dump_certificate(FILETYPE_PEM, self.cert))

  27.        with open(self.cert_file_path, 'wb+') as f:

  28.            f.write(dump_certificate(FILETYPE_PEM, self.cert))

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

 
   
   
 
  1. def _sign_ca(self,cn,cnp):

  2.        #使用合法的CA证书为代理程序生成服务器证书

  3.        # create certificate

  4.        try:

  5.            key = PKey()

  6.            key.generate_key(TYPE_RSA, 2048)

  7.            # Generate CSR

  8.            req = X509Req()

  9.            req.get_subject().CN = cn

  10.            req.set_pubkey(key)

  11.            req.sign(key, 'sha256')

  12.            # Sign CSR

  13.            cert = X509()

  14.            cert.set_version(2)

  15.            cert.set_subject(req.get_subject())

  16.            cert.set_serial_number(self.serial)

  17.            cert.gmtime_adj_notBefore(0)

  18.            cert.gmtime_adj_notAfter(31536000)

  19.            cert.set_issuer(self.cert.get_subject())

  20.            ss = ("DNS:%s" % cn).encode(encoding="utf-8")

  21.            cert.add_extensions(

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

  23.            cert.set_pubkey(req.get_pubkey())

  24.            cert.sign(self.key, 'sha256')

  25.            with open(cnp, 'wb+') as f:

  26.                f.write(dump_privatekey(FILETYPE_PEM, key))

  27.                f.write(dump_certificate(FILETYPE_PEM, cert))

  28.        except Exception as e:

  29.            raise Exception("generate CA fail:{}".format(str(e)))

最后

  • 回复【1】:领取 Python数据分析 教程大礼包

  • 回复【2】:领取 Python Flask 全套教程

  • 回复【3】:领取 某学院 机器学习 教程

  • 回复【4】:领取 爬虫 教程

  • 回复【5】:领取编译原理 教程

知识星球已经50多人了,随着人数的增多,价格之后会上涨,越早关注越多优惠。星球的福利有很多:

  • 比如上面的教程,已经提前在知识星球中分享

  • 可以发表一些问题,大家一块解决

  • 我之后写的电子书,录制的教学视频,对于知识星球的朋友都是优惠的(基本上免费)

  • 一些节假日会给大家发个红包或者赠书


书籍推荐