はじめに
ユーザー入力をそのままCSVに出力すると、セルの先頭が = で始まる値がExcelで数式として実行される——いわゆるCSV数式インジェクション。Excelが実際にどう解釈するのかをCOM経由の実機で確認し、Python側の無害化処理とセットで整理した。
環境
- Windows 11(ビルド 10.0.26200)
- Python 3.11(
csvモジュール。標準ライブラリ) - Microsoft Excel 16.0(COM経由で開いてセルの解釈を確認)
やったこと
1. 危険なCSVを書き出す
import csv
rows = [
["name", "memo"],
["通常の入力", "こんにちは"],
["攻撃1", "=1+1"],
["攻撃2", '=HYPERLINK("http://example.com","クリック")'],
["攻撃3", "+1+1"],
["攻撃4", "-1+1"],
["攻撃5", "@SUM(1,1)"],
]
with open("test.csv", "w", encoding="utf-8-sig", newline="") as f:
csv.writer(f).writerows(rows)
(utf-8-sig はExcelで日本語が化けないためのBOM付き。ここではBOMが有効に働いている。)
2. Excelで開いてセルの中身を確認
PowerShell + COMで、CSVを開いた直後のセルを調べる。
$xl = New-Object -ComObject Excel.Application
$xl.Visible = $false
$wb = $xl.Workbooks.Open((Resolve-Path .\test.csv))
$ws = $wb.Worksheets(1)
2..7 | ForEach-Object {
"B$_ : Formula=[$($ws.Cells.Item($_, 2).Formula)] Value=[$($ws.Cells.Item($_, 2).Value2)]"
}
$wb.Close($false); $xl.Quit()
実行結果:
B2: Formula=[こんにちは] Value=[こんにちは]
B3: Formula=[=1+1] Value=[2]
B4: Formula=[=HYPERLINK("http://example.com","クリック")] Value=[クリック]
B5: Formula=[=1+1] Value=[2]
B6: Formula=[=-1+1] Value=[0]
B7: Formula=[=SUM(1,1)] Value=[2]
= 以外に + - @ の3文字も数式として評価された。特に +1+1 は =1+1 に正規化されて値が 2 になっている。HYPERLINKは表示が「クリック」というリンクになる。
3. 無害化して同じ確認をする
先頭が危険文字なら、'(アポストロフィ)を付けて文字列であることを明示する。
def sanitize(value: str) -> str:
if value and value[0] in "=+-@\t\r":
return "'" + value
return value
with open("test_sanitized.csv", "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f)
for r in rows:
w.writerow([sanitize(c) for c in r])
Excelで開いた結果:
B3: Formula=['=1+1] Value=['=1+1]
B4: Formula=['=HYPERLINK("http://example.com","クリック")] Value=['=HYPERLINK("http://example.com","クリック")]
B5: Formula=['+1+1] Value=['+1+1]
B6: Formula=['-1+1] Value=['-1+1]
B7: Formula=['@SUM(1,1)] Value=['@SUM(1,1)]
数式として実行されなくなった(FormulaにもValueにも計算結果が入っていない)。
ハマった点
-
危険なのは
=だけではない。実測で+-@も式として評価された。OWASPなどではタブ(\t)やCR(\r)を先頭に付ける手口も知られており、サニタイズは「先頭1文字の集合」で判定する。 -
無害化の
'はCSVインポート時に見える。Excelで手入力する場合はアポストロフィが非表示になるが、CSVインポートでは文字としてそのまま表示される(実測)。見た目を優先するなら、アプリ側の表示で1文字を外す・そもそもCSVを直接Excelで開かせない、といった判断が要る。安全性を優先して「少し汚く見える」を受け入れるのも有効。 -
csv.writerは引用符を自動処理するが、式の評価は防げない。"=HYPERLINK(..."のようにクォートされていても、ExcelはCSVを読むときにセル文字列として解釈し直すため、数式は実行されてしまう。 - サニタイズは「出力の直前に1箇所」で。DBや内部データは生のまま持ち、CSVへ書き出す瞬間だけ無害化するのが事故りにくい。
まとめ
- Excelは
=だけでなく+-@先頭のセルも数式として評価する(実機で確認)。 - CSV出力の直前で「先頭の危険文字に
'を付ける」サニタイズを行う。 - 無害化後は
'が見えることを把握しておく(安全側のトレードオフ)。 - サニタイズ関数はエクスポート処理に集約し、データ本体は汚さない。