- 投稿日:
前職で古いWebシステムのマイグレーションしてた時に思ったのだが、SameSite CookieがあればCSRF Tokenは最早いらないのではないか?というのに気が付いたので調べてみた話。
結論として言うとEdgeではPOSTリクエストでCookieが飛ぶ事はないので、CSRF Tokenを考慮する必要はなさそうに思える。
理由としてはCookieでSameSite=strict
を指定している場合、クロスドメインでのCookie送信がブロックされるという話が事実であればCSRF Tokenは不要であるはずだからだ。
これによって悪意のあるサイトからAjaxやリダイレクト、フォームなどを使った攻撃をした場合にサーバーにCookieが送信されなくなる筈なので、理論上は問題なくなる。
以下では実際にCookieが送信されるかどうかの動きを見てみた。確認環境はMicrosoft Edge 121.0.2277.83。方式のリンクはシーケンス図になっている。
シーケンス図に書いてあるSameSite={var}
の{var}
にはstrict
、lax
が入る。確認内容としてはシーケンスの一番最後のHTTPリクエストでCookieが送信されているかどうかを見ている。none
は面倒なので見ていない。
方式 | SameSite=strict | SameSite=lax |
---|---|---|
HTTP 302 REDIRECT | 送信される | 送信される |
アンカーリンクのクリック | 送信されない | 送信される |
アンカーリンクのクリック→HTTP 302 REDIRECT | 送信されない | 送信される |
location.hrefでの移動 | 送信されない | 送信される |
location.hrefでの移動 →HTTP 302 REDIRECT | 送信されない | 送信される |
FORMタグのsubmit(GET) | 送信されない | 送信される |
FORMタグのsubmit(POST) | 送信されない | 送信されない |
SameSite=strict
だと外部サイトから開いたときにCookieが送信されないため、Google検索などから流入された時のログイン状態に不都合が出ると思われるので、その様なケースではSameSite=lax
が無難だろう。
SameSite=lax
の場合、GETリクエストではCookieが送信されるため、CSRF攻撃を受ける可能性があるが、GETで更新系処理をしない限り基本は問題にならないと思われる。
参考
- 2022年1月においてCSRF未対策のサイトはどの条件で被害を受けるか | 徳丸浩の日記
- Cookies: HTTP State Management Mechanism (httpwg.org)
- httpwgによるドラフトの仕様(ブラウザがこれを守るかどうかはまた別の話)
- 投稿日:
サーバーサイドでMarkedを使いたかったので。Node.js 20系はC++やclangのバージョンが古くエラーになると思われるので18系にした
前提
- さくらのレンタルサーバースタンダードプラン
- zshを利用している
手順
所要時間はさくらのレンタルサーバースタンダードプランで2時間丁度くらい
- nodebrewを落としてくる
wget git.io/nodebrew
.zshrc
にパスを通すPATH=${HOME}/.nodebrew/current/bin:${PATH}
コンパイルして使えるようにする
perl nodebrew setup
## バージョン確認
# nodebrew ls-remote
## プロセスが殺されるのを防ぐためにniceで優先度を下げている
## コンパイルには2h程かかる
nice -n 20 nodebrew compile v18.19.0
nodebrew use v18.19.0
試したけどうまくいかなかったやつ
nvm
nice
が効かないのでプロセスキルされて終わる
フルスクラッチコンパイル
OpenSSL周りに何か問題があるらしくコンパイルがこける。バージョンが合ってないのかも
# OpenSSL
wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz
tar -zxvf openssl-1.1.1w.tar.gz
cd openssl-1.1.1w
./config --prefix=/home/$USERNAME/openssl --openssldir=/home/$USERNAME/local/openssl
make
make install
# Node.js
wget https://nodejs.org/dist/v18.9.1/node-v18.9.1.tar.gz
tar -zxvf node-v18.9.1.tar.gz
cd node-v18.9.1
./configure --shared-openssl --shared-openssl-includes=/home/$USERNAME/openssl/include/
export LD_LIBRARY_PATH=/home/$USERNAME/openssl/lib
make install DESTDIR=/home/$USERNAME/local PREFIX=
所感
Node.jsなので負荷が心配だったが、Markedでブログ記事をMarkdownからHTMLに落とす程度なら問題なく動いて安心した。
- 投稿日:
コマンド体系が独特なので毎回忘れる。使い方はman find
で出す。find --help
してもマニュアルは出てこない
目的 | コマンド |
---|---|
通常検索 | find . -name "*hoge" |
正規表現検索 | `find . -type f |
typeで指定できる内容は以下の通り
type | 内容 |
---|---|
b | block (buffered) special |
c | character (unbuffered) special |
d | directory |
p | named pipe (FIFO) |
f | regular file |
l | symbolic link |
s | socket |
D | door (Solaris) |
正規表現で検索するオプションもあるようだが、直感的でないのでgrepで絞ったほうが早い。
- 投稿日:
生のHTTPメッセージを見たい時に使える方法。devtoolsではフォーマットされたログしか見れないが、生のテキストを見たい時に使える。具体的には以下の画像のような奴だ。
よく忘れるのでメモとして残しておく
確認環境
Env | Ver |
---|---|
Google Chrome | 120.0.6099.225 |
Microsoft Edge | 120.0.2210.144 |
やり方
chrome://net-internals/
にアクセスするとログ収集ができるので、ログを集めたらイベントビューワで見ればよい。なおイベントビューワは外部サイトなので注意。
以前はブラウザ内で完結していて、リアルタイムに見れたはずだが、何故かできなくなっていた。
Edgeの場合はedge://net-internals/
でもアクセスできるが、特にこだわりがなければ汎用性の高いchrome://net-internals/
でよいと思う。
- 投稿日:
adiaryの改造でやったのでメモがてら
環境
Env | Ver |
---|---|
Microsoft Edge | 120.0.2210.144 |
PHP | 8.2.10 |
サンプルコード
FORM形式での送信
Web標準でサポートされているため実装が非常に容易。特に理由がなければこれでいい
HTMLコード
<textarea onpaste="handlePasteForm(event)"></textarea>
JSコード
fetch()
のヘッダ指定がないが、 fetch()
を使う場合はFormDataを使うと勝手に生えるので気にしなくていい。他にもXMLHttpRequest.send()
やNavigator.sendBeacon()
でも生えるらしい。
今回試したEdgeではバウンダリー文字列もちゃんと生えていた。
/** @param {ClipboardEvent} e */
const handlePasteForm = (e) => {
if (e.clipboardData.files.length) {
const pasteFile = e.clipboardData.files[0];
const form = new FormData();
form.append('image', pasteFile);
fetch('./test.php', {
method: 'POST',
body: form
});
} else {
// 何もしない
}
};
PHPコード
Content-Type
がmultipart/form-data
の時だけ中身が入ってくる
参考:https://www.php.net/manual/ja/reserved.variables.files.php
<?php
$file_name = $_FILES['image']['name'];
move_uploaded_file($_FILES['image']['tmp_name'], './'. $file_name);
>
$_FILES
の中身はvar_dumpした限りこんな感じだった
array(1) {
["image"]=>
array(6) {
["name"]=>
string(9) "image.png"
["full_path"]=>
string(9) "image.png"
["type"]=>
string(9) "image/png"
["tmp_name"]=>
string(29) "C:\env\msys64\tmp\php3B90.tmp"
["error"]=>
int(0)
["size"]=>
int(3988)
}
}
JSON形式での送信
Web標準ではないので、これといったやり方もなく、正直面倒だが、どうしてもJSONで送らないといけないときに。
HTMLコード
<textarea onpaste="handlePasteJson(event)"></textarea>
JSコード
HTTPではバイナリを送ることができないため、Base64にエンコードして送る。Base64エンコードに含まれる情報はファイルバイナリのみ。もしFORM送信のようにファイル名も送りたければ、別途送ってやる必要がある。
/** @param {File} file */
const encodeBase64 = (file) => {
return new Promise ((resolve, reject) => {
const fr = new FileReader();
fr.onload = () => {
const data = fr.result.split(',')[1];
resolve(fr.result.split(',')[1]);
};
fr.onerror = (ev) => {
reject(ev);
};
fr.readAsDataURL(file);
});
};
/** @param {ClipboardEvent} e */
const handlePasteJson = async (e) => {
if (e.clipboardData.files.length) {
const pasteFile = e.clipboardData.files[0];
const base64File = await encodeBase64(pasteFile);
fetch('./test.php', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({ image: base64File })
});
} else {
// 何もしない
}
};
PHPコード
JSONリクエストを取るときはfile_get_contents('php://input')
を利用する。$_POST
はContent-Type
がapplication/x-www-form-urlencoded
ないしmultipart/form-data
の時しか取れないからだ。つまりFORMリクエスト以外はfile_get_contents('php://input')
で取ると覚えておけばよいだろう。
参考:https://www.php.net/manual/ja/wrappers.php.php#wrappers.php.input
以下のコードはかなりいい加減なので余りアテにしてはいけない(特に拡張子取得の下りがガバガバすぎる)が、取り合えずpngかjpegかgifが送られてきた場合は動いてくれる筈だ。
<?php
// 連想配列にするため、json_decode()の第二引数をtrueにする(指定しないとstdClassになる
$json = json_decode(file_get_contents('php://input'), true);
// Base64エンコードを解除する
$file = base64_decode($json['image']);
// 一時ファイルに落とす
file_put_contents('json_upload', $file);
// 拡張子判別
$mime = mime_content_type('json_upload');
// 拡張子取得
$ext = explode('/', $mime);
// 拡張子付きにリネーム
rename('json_upload', 'image.'. $ext[1]);
参考
JSONでファイルを投げる方式での実装状況については以下の記事が参考になる。
WebAPI でファイルをアップロードする方法アレコレ #API - Qiita
一気通貫で動くサンプルコード
今までの実装が全部動かせるサンプル
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if(preg_match('/^multipart\/form-data;/', $_SERVER['CONTENT_TYPE'])) {
// form
$file_name = $_FILES['image']['name'];
move_uploaded_file($_FILES['image']['tmp_name'], './'. $file_name);
} else {
// json
$json = json_decode(file_get_contents('php://input'), true);
$file = base64_decode($json['image']);
file_put_contents('json_upload', $file);
$mime = mime_content_type('json_upload');
$ext = explode('/', $mime);
rename('json_upload', 'image.'. $ext[1]);
}
} else {
?>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Upload test</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<script>
/** @param {File} file */
const encodeBase64 = (file) => {
return new Promise ((resolve, reject) => {
const fr = new FileReader();
fr.onload = () => {
const data = fr.result.split(',')[1];
resolve(fr.result.split(',')[1]);
};
fr.onerror = (ev) => {
reject(ev);
};
fr.readAsDataURL(file);
});
};
/** @param {ClipboardEvent} e */
const handlePasteForm = (e) => {
if (e.clipboardData.files.length) {
const pasteFile = e.clipboardData.files[0];
const form = new FormData();
form.append('image', pasteFile);
fetch('./test.php', {
method: 'POST',
body: form
});
} else {
// 何もしない
}
};
/** @param {ClipboardEvent} e */
const handlePasteJson = async (e) => {
if (e.clipboardData.files.length) {
const pasteFile = e.clipboardData.files[0];
const base64File = await encodeBase64(pasteFile);
fetch('./test.php', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({ image: base64File })
});
} else {
// 何もしない
}
};
</script>
</head>
<body>
<div>Form:<textarea onpaste="handlePasteForm(event)"></textarea></div>
<div>Json:<textarea onpaste="handlePasteJson(event)"></textarea></div>
</body>
</html>
<?php
}
?>