憑證綁定 Certificate Pinning 分析使用 Curl

最近因為需要做了一點 Pining 分析,
Pinging 主要是要讓對方沒有辦法做中間人攻擊,
那主要測試方法就是先用 Curl 做原本的動作,
範例以 Github API 為例子

clarence:~$ curl -i https://api.github.com/users/octocat/orgs
HTTP/1.1 200 OK
Server: GitHub.com
Date: Thu, 08 Nov 2018 06:49:41 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 5
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 46
X-RateLimit-Reset: 1541661588
Cache-Control: public, max-age=60, s-maxage=60
Vary: Accept
ETag: "98f0c1b396a4e5d54f4d5fe561d54b44"
X-GitHub-Media-Type: github.v3; format=json
Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type
Access-Control-Allow-Origin: *
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Content-Security-Policy: default-src 'none'
X-GitHub-Request-Id: D9F0:3598:2E537A:7CFD03:5BE3DC85
view raw sample-1.sh hosted with ❤ by GitHub

此範例是正常使用 API 並且取得 HTTP 200 OK!

clarence:~$ openssl s_client -connect api.github.com:443 2>&1 < /dev/null \
| sed -n '/-----BEGIN/,/-----END/p' \
| openssl x509 -noout -pubkey \
| openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary \
| openssl enc -base64
y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28=
view raw sample-2.sh hosted with ❤ by GitHub

因為之後要使用 SHA256 的值,所以使用 openssl 做 sha256 取得結果 y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28=,用於 pinning 使用。

clarence:~$ curl -I -x 127.0.0.1:8080 https://api.github.com/users/octocat/orgs
HTTP/1.0 200 Connection established
curl: (60) SSL certificate problem: self signed certificate in certificate chain
More details here: https://curl.haxx.se/docs/sslcerts.html
curl performs SSL certificate verification by default, using a "bundle"
of Certificate Authority (CA) public keys (CA certs). If the default
bundle file isn't adequate, you can specify an alternate file
using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
the bundle, the certificate verification probably failed due to a
problem with the certificate (it might be expired, or the name might
not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
the -k (or --insecure) option.
HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.
view raw sample-3.sh hosted with ❤ by GitHub

此範例說明,如果今天有人對某終端做 Proxy 那理論上使用者的 Device 會因為 SSL 不合法而被擋下來。

clarence:~$ curl -k -I -x 127.0.0.1:8080 https://api.github.com/users/octocat/orgs
HTTP/1.0 200 Connection established
HTTP/1.1 200 OK
Server: GitHub.com
Date: Thu, 08 Nov 2018 08:26:53 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 5
Connection: close
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1541669152
Cache-Control: public, max-age=60, s-maxage=60
Vary: Accept
ETag: "98f0c1b396a4e5d54f4d5fe561d54b44"
X-GitHub-Media-Type: github.v3; format=json
Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type
Access-Control-Allow-Origin: *
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Content-Security-Policy: default-src 'none'
X-GitHub-Request-Id: F462:359B:CF82D0:1C05BD1:5BE3F34D
view raw sample-4.sh hosted with ❤ by GitHub

此範例說明,如果今天使用者的憑證被攻擊了,信任了非法的憑證,那就會可以正常存取,並且達到中間人攻擊的效果。

clarence:~$ curl -I --pinnedpubkey "sha256//y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28=" https://api.github.com/users/octocat/orgs
HTTP/1.1 200 OK
Server: GitHub.com
Date: Thu, 08 Nov 2018 08:29:04 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 5
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 56
X-RateLimit-Reset: 1541669152
Cache-Control: public, max-age=60, s-maxage=60
Vary: Accept
ETag: "98f0c1b396a4e5d54f4d5fe561d54b44"
X-GitHub-Media-Type: github.v3; format=json
Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type
Access-Control-Allow-Origin: *
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Content-Security-Policy: default-src 'none'
X-GitHub-Request-Id: F4FA:359B:CF9C3B:1C08FF0:5BE3F3D0
view raw sample-5.sh hosted with ❤ by GitHub

如果今天我們做了 Certificate Pinning,在正常的情況下不影響正常的動作。

clarence:~$ curl -I -x 127.0.0.1:8080 --pinnedpubkey "sha256//y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28=" https://api.github.com/users/octocat/orgs
HTTP/1.0 200 Connection established
curl: (60) SSL certificate problem: self signed certificate in certificate chain
More details here: https://curl.haxx.se/docs/sslcerts.html
curl performs SSL certificate verification by default, using a "bundle"
of Certificate Authority (CA) public keys (CA certs). If the default
bundle file isn't adequate, you can specify an alternate file
using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
the bundle, the certificate verification probably failed due to a
problem with the certificate (it might be expired, or the name might
not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
the -k (or --insecure) option.
HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.
view raw sample-6.sh hosted with ❤ by GitHub

在憑證不信任的情況下,依然會無法正常執行。

clarence:~$ curl -k -I -x 127.0.0.1:8080 --pinnedpubkey "sha256//y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28=" https://api.github.com/users/octocat/orgs
HTTP/1.0 200 Connection established
curl: (90) SSL: public key does not match pinned public key!
view raw sample-7.sh hosted with ❤ by GitHub

今天最重要的地方在這邊,就算信任了錯誤憑證,也還是會正常流程失敗,如此就成功防禦了中間人攻擊。

以上是對 Certificate Pinning 的小小分析。