Webの勉強はじめてみた その28 〜脆弱性の対策〜

N予備校「プログラミング入門Webアプリ」を受講しています。
今回は第3章27〜34節です。

<h2>脆弱性</h2>
OS コマンド・インジェクション
  - 任意の OS のコマンドを実行できてしまう
SQL インジェクション
  - 任意の SQL というデータベースを操作するコマンドを実行できてしまう
ディレクトリ・トラバーサル
  - 任意のファイルを閲覧、操作できてしまう
セッションハイジャック
  - 利用者のセッションが乗っ取られてしまう
クロスサイト・スクリプティング (XSS)
  - スクリプトにより Web サイトの改ざんができてしまう
クロスサイト・リクエストフォージェリ (CSRF)
  - 利用者の意図しない操作がされてしまう
HTTP ヘッダインジェクション
  - 偽ページの表示などができてしまう
クリックジャッキング
  - 利用者の意図しないクリックをしてしまう

これを見ただけでもフレームワークの必要性がわかる。

<h3>OSコマンドインジェクション</h3>
:::note warn
当人ではなく、Webサービスが実行者になってしまう。
クライアントから受け取ったデータを使ってコマンドを実行しないことが重要
:::
<h3>XSS脆弱性</h3>
:::note
Webサービスの外部からの入力で表示が変化する機能において、意図しないHTML, JavaScript, CSSの変更ができる脆弱性
:::

```pug:posts.pug
each post in posts
      p!= post.content
      p 投稿日時: #{post.createdAt}
      hr
```

タグとして認識させる`!=`は非推奨とのこと。
今回は改行させたいだけなので、cssで工夫する。

```
p(style="white-space:pre; overflow:auto;") #{post.content}
```

<h3>コンテンツセキュリティポリシー(CSP)によるXSS脆弱性の対処法</h3>
:::note
CSPとは、Web サイトにおいて信頼できるリソース（CSS や JavaScript、画像ファイルなど）の範囲を設定し、それ以外の読み込みをブラウザ側でブロックする仕組み
:::

`GET`メソッドが実行された時にレスポンスとしてヘッダーに記述。

```javascript
res.writeHead(200, {        
        'Content-Type': 'text/html; charset=utf-8',
        'Content-Security-Policy': 
        "default-src 'self'; script-src https://*; style-src https://*"        
      });
```

インラインスクリプトを禁止。Bootstrapなどのhttpsで始まる外部ファイルの許可。

<h3>メッセージダイジェスト</h3>
:::note
任意の長さのデータを、そのデータを代表する値に変換する関数のことをハッシュ関数、結果をハッシュ値という。
メッセージダイジェストとは、ハッシュ関数を使って作られたそのパスワードの変換結果。
:::

<h4>ハッシュ関数とセッション</h4>
よく使われるハッシュ関数のアルゴリズム

・MD5
・SHA(SHA-1, SHA-2, SHA-3)
・bcrypt
現在は、SHA-2、bcryptを使う。

:::note
セッションとは、システムにログインまたは接続してから、ログアウトまたは切断するまでの 一連の操作や通信のこと。
セッション固定化攻撃とは、
セッションの乗っ取り(セッションハイジャック)を行うための攻撃手法の 1 つで、外部から指定可能なセッションの識別子を利用してセッションハイジャックを行う。
:::

今回はトラッキングCookieとユーザー名とランダムな数値(`secretKey`)からハッシュ値を作る。

```javascript
const crypto = require('crypto');
/**
 * ハッシュを生成
 * @param {String} originalId 
 * @param {String} userName 
 * @returns {String} Hash値
 */
function createValidHash(originalId, userName) {
  const sha1sum = crypto.createHash('sha1');
  sha1sum.update(originalId + userName + secretKey);
  return sha1sum.digest('hex');
}
```

`secretKey`は、`crypto.randomBytes(256).toString('hex')`を実行して取得。

<h3>CSRF(シーサーフ)脆弱性</h3>
:::note
クロスサイト・リクエストフォージェリ脆弱性
偽サイトを通じて外部のサイトからリクエストを送ることで、重要な処理を誤って起こさせる脆弱性
:::

```html
<form action="http://localhost:8000/posts?delete=1" method="post">
    <input type="hidden" name="content" value="32">
    <button type="submit">ボタンを押してみてください</button>
  </form>
```

全く別のサイトから投稿や削除ができてしまう。
対策として、ワンタイムトークンを使う。

ページへアクセスする際、必ず`GET`メソッドが実行されている。それを利用してワンタイムトークン(CSRFトークン)を毎回発行し、一致しない場合は不正アクセスとする。

`GET`時

```javascript
const oneTimeTokenMap = new Map(); // キーをユーザー名、値をトークンとする連想配列
const oneTimeToken = crypto.randomBytes(8).toString('hex');
oneTimeTokenMap.set(req.user, oneTimeToken);
res.end(pug.renderFile('./views/posts.pug', { 
  posts, 
  user: req.user,
  oneTimeToken  
}));
```

`POST`時

```javascript
body = Buffer.concat(body).toString();
const params = new URLSearchParams(body);
const content = params.get('content');
const requestedOneTimeToken = params.get('oneTimeToken');

if(!(content && requestedOneTimeToken)){
  util.handleBadRequest(req, res);
} else {
  if(oneTimeTokenMap.get(req.user) === requestedOneTimeToken){
    // DataBaseへの登録
    Post.create({
      content,          
      trackingCookie: trackingId,
      postedBy: req.user
    }).then(() =>{
      // Token削除
      oneTimeTokenMap.delete(req.user);
      console.info(`投稿されました: ${content}`);       
      handleRedirectPosts(req, res);
    });
  } else {
    util.handleBadRequest(req, res);
  }
}      
```

<h2>Bootstrap</h2>
:::note
どのようなデバイスでページを見ても適切なデザインとなるような 
HTML, CSS, JavaScript を提供する Web ページ作りのための部品集。CSSフレームワーク。
:::

```pug:posts.pug
link(rel="stylesheet",
    href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css",
    integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm",
    crossorigin="anonymous")

    script(src="https://code.jquery.com/jquery-3.4.1.slim.min.js")

// 中略
    script(src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js",
    integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl",
    crossorigin="anonymous")
```

ネットワーク経由とソースをダウンロードしてくるのと方法はあるけれど、どっちがいいとかあるんだろうか。

<h2>Herokuでデータベースを使う</h2>
`heroku addons:create heroku-postgresql:hobby-dev`
`hobby-dev`はフリープラン。

```javascript
const dialectOptions = {
  ssl: {
    require: true,
    rejectUnauthorized: false
  }
}
const sequelize = process.env.DATABASE_URL ?
  // 本番環境
  new Sequelize(
    process.env.DATABASE_URL, 
    {
      logging: false,
      dialectOptions
    }
  )
  :
  // 開発環境
  new Sequelize(
    'postgres://postgres:postgres@db/secret_board',
    {
      logging: false      
    }
  );
```

環境変数に値が入ってるかどうかで本番かテスト環境かを判断。

<h2>まとめ</h2>
今回で第3章は終わり。
これだけ対策してもまだまだ脆弱性があるとか言われると、ソースコードも煩雑になっていくのは目に見えていて、それをなんとかしてくれるとかもうフレームワークすごいとなるわけで。ワクワクが止まらない。