1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

fallowのsecurity機能でJS/TSの脆弱性コード候補を検知

1
Posted at
  • 安全なコードを書きたくても、レビューや脆弱性診断まで気付けないことは多いです。
  • 今回は、javascript/typescript向けの静的解析ツール「fallow」のsecurity機能で危険な箇所の「候補」を出し、自分で確認して直す流れを試します。

環境

  • Node.js v24.20.0
  • npm 11.9.0
  • fallow 3.30.0

手順

fallow導入

  • プロジェクトを作成し、fallowを入れます。
mkdir fallow-sample && cd fallow-sample
mkdir src
npm init -y
npm install --save-dev fallow
npx fallow init
  • npx fallow init で .fallowrc.json が作られます。

検査カテゴリの設定

  • .fallowrc.json を次の内容に書き換えます。
  • security.categories.include で、検査するカテゴリを選びます。
.fallowrc.json
{
  "$schema": "./node_modules/fallow/schema.json",
  "security": {
    "categories": {
      "include": [
        "dangerous-html",
        "code-injection",
        "command-injection",
        "sql-injection",
        "ssrf",
        "path-traversal",
        "open-redirect",
        "weak-crypto",
        "hardcoded-secret"
      ]
    }
  }
}
  • カテゴリの一覧:公式ドキュメント
  • カテゴリの詳細:npx fallow explain <カテゴリ名>(例:npx fallow explain dangerous-html)
  • 今回のサンプルで使うカテゴリ:
    • command-injection:コマンドインジェクション
    • sql-injection:SQLインジェクション
    • dangerous-html:XSS
    • hardcoded-secret:秘密鍵の直書き

include に書いていないカテゴリは検出されません。hardcoded-secret も、書いたときだけ動きます。

脆弱なサンプルコード用意

  • 動作確認用に、脆弱なコードを4つ用意します。

コマンドインジェクション

  • src/command.tsを作成して、以下の内容を記述します。
import { exec } from "node:child_process";

export function pingHost(name: string) {
  exec("ping -c 1 " + name, (err, stdout) => {
    console.log(stdout);
  });
}

name に ; rm -rf / を渡されると、任意のコマンドが実行されます。

SQLインジェクション

  • src/sql.tsを作成して、以下の内容を記述します。
type Db = { query: (sql: string) => unknown };

export function findUser(db: Db, id: string) {
  return db.query(`SELECT * FROM users WHERE id = ${id}`);
}

id に 1 OR 1=1 を渡されると、全ユーザーが取得されます。

XSS

  • src/xss.tsを作成して、以下の内容を記述します。
export function showComment(text: string) {
  const el = document.querySelector("#comment");
  if (el) el.innerHTML = text;
}

text に <img src=x onerror=...> を渡されると、閲覧者のブラウザでスクリプトが動きます。

秘密鍵直書き

  • src/secret.tsを作成して、以下の内容を記述します。
export const stripeSecretKey = "sk_live_xxxxxxxxxxxxxxxxxx";

ソースに書くと、gitの履歴に残り続けます。値はダミーです。

検出を確認

  • 以下を実行します。
npx fallow security
  • 私の環境では、4件の候補が出ました。
[L] low OS command injection sink (CWE-78)  src/command.ts:4
[L] low Hardcoded secret candidate (CWE-798)  src/secret.ts:1
[L] low SQL injection sink (CWE-89)  src/sql.ts:4
[L] low Dangerous HTML sink (CWE-79)  src/xss.ts:3
  • 各候補には、根拠(evidence)と次にやること(Next)などが付きます。

修正して再確認

コマンドインジェクション

  • src/command.tsを以下の内容に書き換えます。
import { execFile } from "node:child_process";

export function pingHost(name: string) {
  if (!/^[a-zA-Z0-9.-]+$/.test(name)) throw new Error("bad host");
  execFile("ping", ["-c", "1", name], (_err, stdout) => console.log(stdout));
}

シェルを通さず、引数を配列で渡します。入力の形式も検証します。

SQLインジェクション

  • src/sql.tsを以下の内容に書き換えます。
type Db = { query: (sql: string, params: unknown[]) => unknown };

export function findUser(db: Db, id: string) {
  return db.query("SELECT * FROM users WHERE id = ?", [id]);
}

値を文字列に埋め込まず、プレースホルダで渡します。

XSS

  • src/xss.tsを以下の内容に書き換えます。
export function showComment(text: string) {
  const el = document.querySelector("#comment");
  if (el) el.textContent = text;
}

HTMLとして解釈させず、テキストとして入れます。

秘密鍵直書き

  • src/secret.tsを以下の内容に書き換えます。
export const stripeSecretKey = process.env.STRIPE_SECRET_KEY;

環境変数から読みます。

再実行

  • 再度npx fallow securityを実行して検知されないことを確認します。
Result: 0 security items to check.

まとめ

  • fallow securityは、脆弱性を断定せず、根拠つきの候補を出すツールです。判定は自分で行う役割分担が明確で、続けやすいと感じました。
  • 本記事では触れませんでしたが、gitのpre-commitやCI、AIにも組み込めるので、危険コードをpush前や公開前に防ぐ仕組みも構築できると感じました。
  • ただし、カテゴリ設定に依存し、見逃しや死角もあります。レビューや他の診断と併用するのが良さそうです。

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?