把 HTTP 站台逐步換向 HTTPS 站台的步驟

Jerry Qu 寫的「关于启用 HTTPS 的一些经验分享」這篇文章講了要怎麼將 HTTP 站台逐步換成 HTTPS 站台的方式 (以及工具)。

一開始會遇到 Mixed Content,瀏覽器預設值不會直接全部擋掉,而是會放行圖片類資源 (但是出現對應的警告)。然後可以用 upgrade-insecure-requests 來幫助邊換,讓 url 裡指定 http 的自動連到 https。

當全站把 url 都修完後,接著就可以考慮用 HSTS 強制全上 HTTPS。

做到這邊的安全性已經到一定程度了,接下來要不要進 HSTS Preload List 就看大家自己的想法了。

用 Unicode 字元當作短網址編碼的服務...

Hacker News Daily 上看到的服務:「9m URL Shortener」。

居然用 Unicode 短網址當作編碼的部份... 是很有趣啦,不過因為難以輸入,只適合用在 internet 上的傳遞?而不是讓人輸入的...

有些字在我的電腦上還出不來 XDDD

擋 Open Redirect 的問題...

Open Redirect 的問題可以參考:

這兩個連結。主要是要避免 phishing 的問題上。

一開始是以「只允許 / 開頭」為條件過濾,但 protocol-relative 的 //www.example.com 可以繞開。

如果變成「只允許 / 開頭,但不允許 // 開頭」,是不是就沒事了呢?

在「Evolution of Open Redirect Vulnerability.」這邊又看到新招:「/\www.example.com」。

想要用 parse_url() 檢查?沒問題:

$ php -a
Interactive mode enabled

php > var_dump(parse_url("/\\www.example.com"));
array(1) {
  'path' =>
  string(17) "/\www.example.com"
}

但實際測試會發現 IE8 與 Google Chrome 都會跳到 www.example.com (沃槽),其他瀏覽器就先不測了 ~_~

不過原文說的 ///www.example.comPHP 上測試應該是不會過的?

$ php -a
Interactive mode enabled

php > var_dump(parse_url("///www.example.com"));
bool(false)

反正又冒出一堆問題要處理了 ~_~

Amazon CloudFront 上 Protected Content 的 URL Sign...

Amazon CloudFront 也可以設定要簽名才能抓檔案,只是 URL Sign 設計的觀念跟 Amazon S3 完全不一樣,這不一致的調調很... 詭異...

大致上有這些差異:

  • Amazon S3 用的是 HMAC-SHA1 的機制簽名 (shared secret,也就是 Amazon S3 與你都有同一把 key),而 Amazon CloudFront 則是用 RSA key 簽名 (public key,也就是 Amazon CloudFront 存放 public key,你自己存放 private key)。
  • 也因為是使用 RSA key,有人會誤解跟 Amazon EC2 用的 RSA key 相同。但實際上需要在 Security Credentials 頁裡設,有專門對 Amazon CloudFront 用的 RSA key 的段落。
  • 自己對 Base64 編碼再處理,避開使用 += 以及 /。但不是有標準可以用嗎,為什麼要自己發明呢...
  • 引入 Policy 的彈性機制,不僅可以對時間控制,也可以對 IP address 控制,但 Policy 這是帶在 URL 裡傳進去的... 你可以看到我的程式碼內產生出 $json_str 後簽完名帶到 URL 內了。

官方的 CloudFront Signed URLs in PHP 這篇的範例程式碼其實很清楚了,要直接拿去用其實也沒麼問題。我自己整理後是這樣:

<?php

$key_pair_id = 'APKA...';
$pem_file = '';
$resource = 'http://test2-cdn.gslin.org/test.txt';

$expires = time() + 3600;

$json_str = json_encode(
    array(
        'Statement' => array(
            array(
                'Resource' => $resource,
                'Condition' => array(
                    'DateLessThan' => array(
                        'AWS:EpochTime' => $expires
                    )
                )
            )
        )
    ),
    JSON_UNESCAPED_SLASHES
);

$buf = file_get_contents($pem_file);
$key = openssl_get_privatekey($buf);

openssl_sign($json_str, $signed_policy, $key, OPENSSL_ALGO_SHA1);

openssl_free_key($key);

$signature = str_replace(
    array('+', '=', '/'),
    array('-', '_', '~'),
    base64_encode($signed_policy)
);

echo "${resource}?",
    "Expires=${expires}&",
    "Signature=${signature}&",
    "Key-Pair-Id=${key_pair_id}\n";

反正你搞不太懂 Amazon 為什麼要這樣設計的... =_=

Amazon S3 上 Protected Content 的 URL Sign...

Amazon S3 可以在上傳時設定為 non-public,這種檔案如果要讓使用者讀取,就必須透過 URL 簽名的方式...

官方的文件是「Authenticating REST Requests」這篇,不過官方文件把所有細節都寫上去,如果第一次接觸的人反而不知道要怎麼辦...

Thomas Riboulet 給了一個 Quickstart 的範例:「Signing Amazon S3 URLs」,裡面雖然是 Ruby code,不過 code 的邏輯很簡單...

以 test.gslin.org 為例,想要產生出從現在開始 3600 秒內有效的 url 可以這樣寫,用過一次後再回去看官方文件,就會發現其實就是官方文件的「REST Authentication Example 3: Query String Authentication Example」這段,如果以 PHP 寫會長這樣:

<?php

$access_key = "";
$secret_key = "";

$timestamp = time() + 3600;
$data = "GET\n\n\n${timestamp}\n/test.gslin.org/test.txt";

$sign = urlencode(
    base64_encode(
        hash_hmac('sha1', $data, $secret_key, true)
    )
);

echo "http://test.gslin.org.s3.amazonaws.com/test.txt?" .
    "AWSAccessKeyId=${access_key}&" .
    "Expires=${timestamp}&" .
    "Signature=${sign}\n";

接下來來研究 CloudFront 的部份...