處理使用者輸入的值

設計一個表單,供使用者輸入文字並將它存檔/顯示, 是一件常做的事...

在開發 web 程式經常會需要讓使用者輸入資料, 並稍後呈現, 以下圖為例, 您先連結到 google 首頁後, 輸入 allenkuo, 並按下按鈕...

就會顯示下圖的查詢結果, 由於 google 的<form>是以 action=get 方式送出, 因此您可以在網址列看到 allenkuo 這個值。 google 在網頁的 textbox 也會顯示您剛才輸入的值, 以方便您繼續做其他的查詢

 

像這類允許使用者輸入, 並稍後呈現在網頁中的功能, 算是開發 web 的人經常需要寫的, 也有必要將它安全地處理, 或許您會覺得它是很簡單的功能, 但其實不少人都沒有做好哦!! 接下來我找些網頁來做說明, 為了保護及尊重別人的網站, 我會將網址及網頁內容做一點處理, 各位只需要看看各網站可能會有什麼問題即可, 萬一您看出我指的是哪些網站, 也請不要惡意去破壞對方的網站

範例1:

正常網址 :http://www..../xxx.jsp?b_c_id=54&menu_id=4, 畫面是

若手動改為http://www..../xxx.jsp?b_c_id=54&menu_id=4a, 畫面變成

原因應該是程式忘了驗證 Request "menu_id" 的值是不是數字

 

 範例2:

正常網址 :http://www..../newsxxx.asp?EVENT_CATEGORY=活動訊息&EVENT_NAME=【活動訊息】...虛擬未來新世代...研討會!!&..., 網頁裡呈現的部份畫面是

若手動改為 http://www..../newsxxx.asp?EVENT_CATEGORY=活動訊息&EVENT_NAME=Allen最棒&...,  畫面變成

原因應該是網頁裡顯示"目前位置"的程式是直接取用 Request("Event_name") 所致, 如果網址只傳event id,再用程式去 database 擷取活動標題,就不會有這類問題

 

範例3:

我在此網站的網址裡, 修改了參數值, 將Edit=1改成Edit=1', 從網頁的錯誤訊息可以得知程式裡除了沒有判斷Request("Edit")是否為數值之外, 也沒有處理單引號的問題

 

範例4:

我在國內某知名公司的網站輸入查詢關鍵字, 且故意輸入一段 javascript code, 請參考下圖,網頁中真的會執行我輸入的 javascript code, 而網址則顯示成
http://www....com.tw/search/xx?...&keyword=%3Cscript%3Ealert%28%27a%27%29%3B%3C%2Fscript%3E&...
這也意味著我若填入惡意的 javascript code, 並將這網址寄給不知情的第三人時, 若對方只看了網域名稱並信任對方,就click這網址時, 便有可能執行我這段惡意的 javascript,當然啦,我所謂的惡意,絕對不會僅止於 say Hi 而已

 

 

範例5:

 如果您到某網站加入會員, 多半會有"編輯個人基本資料" 的功能, 表單裡會預先填入您當初申請會員的資料, 但有些網站針對這點沒有處理得很好, 例如您在申請時填的值為

日後您要編輯個人基本資料時, 這 TextBox 裡其實不會顯示上述的值,而是顯示成

為什麼呢? 如果您此時檢視網頁原始碼, 應該可以看到類似下列的 HTML 內容

由於您當初輸入值有包含雙引號, 但日後要編輯時, 卻因為它而導致後半段的文字不見了, 如果您沒特別留意, 直接按下"儲存", 後半段的資料便真的消失了, 如果您到 google 網站測試, 如下圖所示

google 的網頁是可以正常顯示的, 這才是正確的運作過程

 

範例6:

我在某網路商店的查詢功能裡, 輸入
asp.net
在查詢結果頁是顯示
 
它算是正常運作, 但我若故意輸入
<img src="http://www.pchome.com.tw/img/pchomelogo.gif">
在查詢結果頁卻顯示

就比較不正確, 理論上應該顯示成
搜尋 : <img src="http://www.pchome.com.tw/img/pchomelogo.gif">

才比較正確 (除非當初客戶就要求這麼呈現,那就另當別論), 而這狀況如果是在 A 國家總統府網站卻顯示 B 國家的國旗時, 問題看起來就比較敏感些了

 


接收 QueryString 值 

取值前, 要先檢查值的內容

有時參數會經由網址來傳遞, 例如您想呈現單筆的最新消息, 網址可能會設計成
http://..../news.aspx?id=99
其中的id=99, 稍後程式會去資料庫取得編號為 99 的最新消息, 並呈現在網頁中

由於使用者很容易便可以修改網址, 因此您要取得值時, 若直接寫成
int newsID = Convert.toInt32( Request["id"] );
便比較容易發生錯誤, 比較安全的做法建議是

  • 檢查有沒有Request.QueryString["id"]
  • 如果有, 使用 int.tryParse() 去試著將它轉型成 int 型別
  • 視需要再增加其他的判斷, 例如若它代表了購買數量,就必需大於 0 或小於庫存量, 若它是news id,就不一定要限制它是否大於零
  • 如果驗證不通過,就做適當的例外處理, 若傳入的值是合理的, 程式才繼續進行

由於取網址上的值是經常需要做的, 建議您將上述的動作包成一支函式, 日後需要時就呼叫它即可, 如果取值之前有先檢查, 那麼就不會發生範例 1, 3 的錯誤了。

 

若資料有私密性, 不宜直接信任網址上的值

若您在撰寫購物車,而目前使用者的訂單編號為199, 您可能設計成
http://..../myOrder.aspx?orderid=199
但這是有風險的,例如使用者只要將它改成
http://..../myOrder.aspx?orderid=198
就很有可能看得到其他客戶購買內容, 因此您這麼設計的時候, 必需做其他的處理, 例如查詢訂單時除了以 order id 為條件時,也要一併查目前使用者 id , 您可以寫成
string sql = "SELECT * FROM Orders WHERE UserID=" + Session["currentUserID"].ToString()  + " AND OrderID=199";
如此一來, 就可以避免 A 客戶看到 B 客戶的訂單內容。

 

盡量不要傳遞資料庫可以取得的資訊

由於將值放在網址裡傳遞, 使用者可以輕易地修改, 因此盡量不要將值以網址傳送, 例如範例 2, 它將最新消息的標題以網址傳送, 間接地也提供別人亂改的機會, 如果網址只傳送id, 再由程式根據 id 去取得最新消息的標題, 就會比較安全些。

 


顯示值

這篇文章有一個假設的前提, 就是假設網站管理者是好人,他不會輸入惡意的文字來害到訪網站的使用者, 並假設可以輸入訊息的使用者, 都是可能有惡意的使用者。

若需要顯示沒有風險的值在網頁中

這算是最常見的狀況, 例如管理者在後台輸入最新消息, 並在前台顯示最新消息的內容, 由於管理者不會害人, 因此您在後台只需要在表單放 TextArea 或 HTML Editor 給管理者輸入最新消息即可(輸入內容包含文字,html tag,甚至 javascript code)。

 

若需要顯示有風險的值在網頁中(1)

範例 4,6, 是將使用者查詢的關鍵字顯示在網頁中, 為了避免使用者輸入 HTML tag,造成版面破壞, 或輸入惡意的 javascript code 而造成第三者受害, 因此顯示這類內容時, 最好寫成

 

string content=".....要顯示在網頁裡且可能有風險的文字...";
Response.Write( Server.HTMLEncode(content) );

若需要顯示有風險的值在網頁中(2)

如果您允許使用者輸入 HTML tag, 但不允許使用者輸入 javascript, 例如您正在做留言板, 它允許使用者輸入粗體字, 甚至貼圖, 那麼就無法用剛才的方法(HTMLEncode),  您可以在表單設計一個 HTML Editor , 並在使用者送出內容後,就先利用 Server.HTMLDecode 將使用者輸入內容解碼後,再檢查輸入內容裡有沒有 javascript code, iframe等, 如果有,就將它刪除,最後才存入db中, 甚至有些 HTML Editor 就內建過濾 script 的功能。

 

若需要顯示有風險的值在網頁中(3)

如果您允許使用者輸入 HTML tag / javascript, 並希望它們能以純文字方式呈現, 例如您正在做一個討論程式的論壇, 它允許使用者輸入程式碼(包含javascript),  您可以在表單設計一個 HTML Editor , 由於使用者在輸入程式碼或html tag時, 都會被 HTML Editor 轉換成安全的碼, 因此在呈現時就不會有問題, 您在呈現時只需要用 Server.HTMLEncode() 即可呈現於網頁中

 

若需要顯示部份的值在網頁中

有時您需要在網站首頁列出 3 則最新消息的標題及內容的前 100 個字, 但由於最新消息很可能是以 HTML tag 存放, 例如<b>xxxx</b>, 那麼您在擷取前100個字時,便可能會只擷取到
<b>xxxx

<b>xxxx<b
而導致網頁的格式被破壞, 就算很幸運地擷取到
<b>xxxx</b><img src="..." />....
那麼在網頁中呈現的也不會是100個字(因為有部份是html tag)

如果您有這個需求,可以考慮在使用者/管理者送出表單時, 就將內容由 html 轉換成純文字並存放在另一個欄位中, 稍後要取出前100個字時, 就以純文字欄位值為準, 有些 HTML Editor 俱備轉換成純文字的功能, 也有人利用 Regex 將字串刪除 html tag, 都可以做到轉換成純文字的功能

 

若需要顯示值在 TextBox 中

在範例 5 中, 若您需要在 TextBox 裡存放值, 在ASP.NET裡是沒問題的, 只需要寫成
txtTitle.Text = "...."; //就算有雙引號也不會有問題, 若您是寫 ASP, 則可以寫成
<input type=text value="<%=HTMLEcode( RS(...)  )%>"> 
就可以正常地呈現雙引號了