上一篇教各位如何將前端配合同構達成如SSR的效果,但是其實那種寫法是有一些資安隱憂需要注意的。
原本是CSR變成SSR的話,就需要考量到Server注入的資料是否有問題,因此有可能會發生XSS的問題。


SEO全系列文章

📌 如何加強網站的SEO - 基礎篇
📌 如何加強網站的SEO - 進階篇
📌 如何加強網站的SEO - SSR與CSR篇
📌 如何加強網站的SEO - 同構篇
📌 如何加強網站的SEO - 框架篇
📌 如何加強網站的SEO - 資安篇


認識Cross-Site Scripting

Cross-Site Scripting又稱為XSS,中文稱作跨網站指令碼,透過程式碼注入的方式,將程式把注入到網頁上面並且執行,而影響到網站的正常運作,XSS變化的方式不少,時常搭配HTML的斷句或是大小寫等…避開一些程式規則。

什麼狀況會發生XSS

XSS有很多案例,以下列出幾個比較重要且經典的

  1. Reflected XSS - 即時反映的script
    有任何可輸入的欄位,都必須小心發生XSS
    通常惡意者會利用回吐的error,從中拿取資料

    1
    2
    3
    4
    <form>
    <input id="test" placeholder="Enter malicious input here" size="30" value="<script>alert('XSS injected!');</script>" />
    <input type="submit" value="Submit" />
    </form>
  2. Stored XSS - 可重複利用的script
    惡意者不一定期望馬上執行這段script,可能會先把惡意程式碼透過可輸入的欄位先從到資料庫內,再到網頁call actions回撈資料後,便會執行那段惡意程式,從中取得部份資訊。
    當然,儲存的XSS可能會有其他變化,例如:cookie,惡意者先把惡意程式存在cookie內,再透過網頁讀取的方式執行這段惡意程式。

    1
    <script>alert(document.cookie)</script>
  3. DOM XSS
    正常情況下,當DOM需要被更新時,前端程式碼並不會發生任何改變。
    但是當惡意者透過一些修改瀏覽網頁的話,會導致程式碼執行的邏輯被改變。
    例如有一個網頁的網址如下:

    1
    http://ex.com/main.html?default=1

惡意者可以透過修改後面的default做出一些變化,甚至可以搭配第二點的方式使用cookie。

1
http://ex.com/main.html?default=<script>alert(document.cookie)</script>

SPA與XSS

這裡以React為例,React能夠預先載入前端網頁,讓使用者能夠使用的更順暢。
而且React也已經實作了大部分的前端邏輯,因此開發者通常不被需要擔心會有XSS的疑慮。
例如我們開發React是以JSX進行開發,所以會先經過一段轉譯
轉譯前的JSX:

1
2
3
4
5
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);

轉譯後的JS:

1
2
3
4
5
const element = React.createElement(
‘h1’,
{className: ‘greeting’},
‘Hello, world!’
);

所以,你只要好好善用React包裝好的功能,幾乎是不用太擔心安全疑慮。
你可能需要擔心的問題:

  1. 使用dangerouslySetInnerHTML
  2. 把機敏資料送到eval()
  3. 使用者的href或是可注入的屬性render

在做SSR的時候,我們在做的其實就是注入script的行為,因此如果你的資料需要過濾。
下面提供幾個考量點

  1. 如果你的專案已經運行一段時間了,可能會有資料庫內涵XSS的隱憂,那麼建議在讀取資料的時候,就需要對資料進行過濾,再找時間把資料庫的資料進行過濾。
  2. 如果專案還在進行開發,那麼可以在使用者儲存資料之前,就做好防呆來避免儲存惡意程式。

如何過濾

之前的同構文章有提到從Node注入資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const injectHTML = (data, {
html, title, meta, script, body, initState
}) => {
meta = decodeEntity(meta);
data = data.replace('<html>', `<html ${html}>`);
data = data.replace(/<title>.*?<\/title>/g, title);
data = data.replace('</head>', `${meta}</head>`);
data = data.replace('<body>', `<body> \n ${script}<script>window.__INITIAL_STATE__ =${JSON.stringify(initState)}</script>`);
data = data.replace(
'<div id="root"></div>',
`<div id="root">${body}</div>`
);
return data;
};

我們從這段程式可以看出,meta和React的INITIAL_STATE很有可能被注入惡意資料。
因此在這邊可以加入過濾的function,判斷內文如果有<符號,就取代成其他編碼方式而不影響呈現。

1
2
3
const filterXSS = (content) => {
return content.replace(/</g, '\\u003c');
}

這樣就可以寫成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const injectHTML = (data, {
html, title, meta, script, body, initState
}) => {
meta = decodeEntity(meta);
data = data.replace('<html>', `<html ${html}>`);
data = data.replace(/<title>.*?<\/title>/g, title);
data = data.replace('</head>', `${filterXSS(meta)}</head>`);
data = data.replace('<body>', `<body> \n ${script}<script>window.__INITIAL_STATE__ =${JSON.stringify(filterXSS(initState))}</script>`);
data = data.replace(
'<div id="root"></div>',
`<div id="root">${body}</div>`
);
return data;
};

等等,那麼script呢

你還在上面的例子發現了什麼嗎? 沒錯,${script}也有可能塞入其他惡意程式
但是這邊有可能是你已經預期會在前端塞入的資料,是你可控制的範圍,而非外來資料
那麼不就不用過濾了嗎?這就要看你可能塞入什麼程式了
這邊可能發生XSS的狀況會是結構化資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Recipe",
"name": "Apple Pie by Grandma",
"author": "Elaine Smith",
"image": "http://images.edge-generalmills.com/56459281-6fe6-4d9d-984f-385c9488d824.jpg",
"description": "A classic apple pie.",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "7462",
"bestRating": "5",
"worstRating": "1"
},
"prepTime": "PT30M",
"totalTime": "PT1H30M",
"recipeYield": "8",
"nutrition": {
"@type": "NutritionInformation",
"calories": "512 calories"
},
"recipeIngredient": [
"1 box refrigerated pie crusts, softened as directed on box",
"6 cups thinly sliced, peeled apples (6 medium)"
]
}
</script>

如果你直接把描述放到結構化資料裡面,不就會發生XSS或是跑版了嗎?
因此如果有需要的話,也可以在這邊去做處理