為什麼你應該關閉 express.js 的延伸格式 query string

要怎麼(不)讓你的 express Hello World app 噴錯

讓我們從一個最基礎的 express app 開始

import * as express from "express";

const app = express();

app.get("/echo", (req, res) => {
    res.end(req.query.data);
});

app.listen(3000, () =>
    console.log("Server is running on http://localhost:3000")
);

問題

Q: 什麼都不做就只是吐出參數中的 data 而以,應該沒問題對吧?
A: 他會因為用戶亂輸入參數吐出 500

不相信?

試試看這個

http://你的網域:3000/echo?data[a]=1

你會得到

TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be one of type string or Buffer. Received type object

But why?

Express.js 預設使用了類似 PHP 的延伸 query string 格式
qs
因此上面的網址中的 query string 會被轉換為

{
    "data": {
        "a": "1"
    }
}

而試圖把物件往 res.end 塞,當然就噴出錯誤了

嘗試修復

Q: 那我們先用 String() 把參數轉 string 是不是就安全了?
A: Well…yes, but actually no. 還是會爆炸

不信?

試試看這個

import * as express from "express";

const app = express();

app.get("/echo", (req, res) => {
    res.end(String(req.query.data));
});

app.listen(3000, () =>
    console.log("Server is running on http://localhost:3000")
);
http://你的網域:3000/echo?data[toString]=1

這次你得到了

TypeError: Cannot convert object to primitive value

但這又是為什麼?

這是因為,當 js 中要把物件轉為 string 時,他會呼叫物件上的 toString 方法, 然而要是你把 toString 屬性覆蓋掉的話,他就炸開了。

正確的修復方式

Part 1

驗證輸入類型,如果不對直接吐出錯誤

app.get("/echo", (req, res) => {
    if (typeof req.query.data) !== 'string') {
        res.status(400).end('what are you doing???')
    }

    res.end(req.query.data);
});

Part 2

關閉 express 的延伸 query string 格式以免以後一樣被亂喂參數
這樣 express 就會改用 nodejs 內建的只支援簡單格式的 querystring 而不是 qs

app.set('query parser', 'simple')

這樣一來,問題終於解決了

Written on August 22, 2019