N予備校「プログラミング入門Webアプリ」を受講しています。
今回は第3章27〜34節です。
脆弱性
OS コマンド・インジェクション - 任意の OS のコマンドを実行できてしまう SQL インジェクション - 任意の SQL というデータベースを操作するコマンドを実行できてしまう ディレクトリ・トラバーサル - 任意のファイルを閲覧、操作できてしまう セッションハイジャック - 利用者のセッションが乗っ取られてしまう クロスサイト・スクリプティング (XSS) - スクリプトにより Web サイトの改ざんができてしまう クロスサイト・リクエストフォージェリ (CSRF) - 利用者の意図しない操作がされてしまう HTTP ヘッダインジェクション - 偽ページの表示などができてしまう クリックジャッキング - 利用者の意図しないクリックをしてしまうこれを見ただけでもフレームワークの必要性がわかる。
OSコマンドインジェクション
:::note warn 当人ではなく、Webサービスが実行者になってしまう。 クライアントから受け取ったデータを使ってコマンドを実行しないことが重要 :::XSS脆弱性
:::note Webサービスの外部からの入力で表示が変化する機能において、意図しないHTML, JavaScript, CSSの変更ができる脆弱性 :::each post in posts
p!= post.content
p 投稿日時: #{post.createdAt}
hr
タグとして認識させる!=
は非推奨とのこと。
今回は改行させたいだけなので、cssで工夫する。
p(style="white-space:pre; overflow:auto;") #{post.content}
コンテンツセキュリティポリシー(CSP)によるXSS脆弱性の対処法
:::note CSPとは、Web サイトにおいて信頼できるリソース(CSS や JavaScript、画像ファイルなど)の範囲を設定し、それ以外の読み込みをブラウザ側でブロックする仕組み :::GET
メソッドが実行された時にレスポンスとしてヘッダーに記述。
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'Content-Security-Policy':
"default-src 'self'; script-src https://*; style-src https://*"
});
インラインスクリプトを禁止。Bootstrapなどのhttpsで始まる外部ファイルの許可。
メッセージダイジェスト
:::note 任意の長さのデータを、そのデータを代表する値に変換する関数のことをハッシュ関数、結果をハッシュ値という。 メッセージダイジェストとは、ハッシュ関数を使って作られたそのパスワードの変換結果。 :::ハッシュ関数とセッション
よく使われるハッシュ関数のアルゴリズム・MD5
・SHA(SHA-1, SHA-2, SHA-3)
・bcrypt
現在は、SHA-2、bcryptを使う。
セッションとは、システムにログインまたは接続してから、ログアウトまたは切断するまでの 一連の操作や通信のこと。
セッション固定化攻撃とは、
セッションの乗っ取り(セッションハイジャック)を行うための攻撃手法の 1 つで、外部から指定可能なセッションの識別子を利用してセッションハイジャックを行う。
今回はトラッキングCookieとユーザー名とランダムな数値(secretKey
)からハッシュ値を作る。
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')
を実行して取得。
CSRF(シーサーフ)脆弱性
:::note クロスサイト・リクエストフォージェリ脆弱性 偽サイトを通じて外部のサイトからリクエストを送ることで、重要な処理を誤って起こさせる脆弱性 :::<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
時
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
時
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);
}
}
Bootstrap
:::note どのようなデバイスでページを見ても適切なデザインとなるような HTML, CSS, JavaScript を提供する Web ページ作りのための部品集。CSSフレームワーク。 :::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")
ネットワーク経由とソースをダウンロードしてくるのと方法はあるけれど、どっちがいいとかあるんだろうか。
Herokuでデータベースを使う
`heroku addons:create heroku-postgresql:hobby-dev` `hobby-dev`はフリープラン。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
}
);
環境変数に値が入ってるかどうかで本番かテスト環境かを判断。