解析經典 ASP 刪除多筆記錄與資料庫連線的常見程式碼片段,說明各行用途、潛在風險 (SQL Injection) 與現代化改寫建議。
片段一:批次刪除記錄 (delete.asp)
<%@ Language=VBScript %><%
Option Explicit
'--- 1. 開啟資料庫連線 (假設在共用 include 檔中定義) ---
Call OpenDataConn()
'--- 2. 接收參數 ---
Dim xxlb, id, ids, i, tt
xxlb = RtnReplaceInt(Trim(Request("xxlb")), 0) ' 自訂函數:整數驗證+預設值
id = ChangeChr(Request("id")) ' 自訂函數:字元過濾/轉義
'--- 3. 分割 ID 字串 (格式: "1,2,3") ---
ids = Split(id, ",")
'--- 4. 迴圈刪除 ---
For i = 0 To UBound(ids)
tt = Trim(ids(i))
' ⚠️ 風險:直接拼接 SQL,若 tt 含惡意代碼將導致 SQL Injection
conn.Execute "DELETE FROM house WHERE id = " & tt
Next
'--- 5. 導回列表頁 ---
Response.Redirect "house.asp?xxlb=" & xxlb
'--- 6. 關閉連線 ---
Call CloseDataConn()
%>
片段二:資料庫連線共用模組 (conn.asp)
<%@ Language=VBScript %><%
Option Explicit
Dim conn
Dim char_str(20) ' 未使用的陣列,可移除
Sub OpenDataConn()
Dim connStr
' Access Jet OLEDB 4.0 連線字串,使用 Server.MapPath 取得實體路徑
connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
Server.MapPath("../../data/fyhouse_free.mdb")
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open connStr
End Sub
Sub CloseDataConn()
If IsObject(conn) Then
If conn.State = 1 Then conn.Close ' adStateOpen = 1
Set conn = Nothing
End If
End Sub
%>
關鍵風險點分析
| 風險 | 程式碼位置 | 攻擊向量 | 後果 |
| SQL Injection | `conn.Execute "DELETE ... WHERE id = " & tt` | `id=1; DROP TABLE house;--` | 資料表被刪除、資料外洩、權限提升 |
| IDOR (不安全直接物件引用) | 無權檢查直接刪除 | 橫向越權刪除他人資料 | 資料完整性破壞 |
| CSRF | 無 Token 驗證 | 誘導管理員點擊惡意連結 | 非預期刪除操作 |
| 連線物件全域變數 | `Dim conn` 在全域 | 併發請求共用同一 Connection 物件 | 「Operation is not allowed when the object is closed」錯誤、資料交叉 |
| 錯誤處理缺失 | 無 `On Error Resume Next` / `Err.Number` 檢查 | 資料庫鎖定、連線失敗時顯示詳細錯誤資訊 | 資訊洩露、使用者體驗差 |
安全重構版 (參數化查詢 + 權限檢查 + 交易)
<%@ Language=VBScript %><%
Option Explicit
Response.Buffer = True
Dim conn, cmd, ids, i, tt, xxlb, userId, isAdmin
'--- 驗證登入與權限 ---
If Session("UserID") = "" Then Response.Redirect "login.asp"
userId = CLng(Session("UserID"))
isAdmin = (Session("Role") = "admin")
'--- 參數處理 ---
xxlb = CLng(Request("xxlb")) ' 強制轉數字,非數字變 0
id = Request("id")
If id = "" Then Response.Redirect "house.asp?xxlb=" & xxlb
ids = Split(id, ",")
'--- 開啟連線 (區域變數,避免併發衝突) ---
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Server.MapPath("../../data/fyhouse_free.mdb")
On Error Resume Next
conn.BeginTrans
Set cmd = Server.CreateObject("ADODB.Command")
cmd.ActiveConnection = conn
cmd.CommandText = "DELETE FROM house WHERE id = ? AND (owner_id = ? OR ? = True)"
cmd.CommandType = 1 ' adCmdText
' 參數:@id, @owner_id, @is_admin
cmd.Parameters.Append cmd.CreateParameter("@id", 3, 1, 4) ' adInteger
cmd.Parameters.Append cmd.CreateParameter("@owner_id", 3, 1, 4)
cmd.Parameters.Append cmd.CreateParameter("@is_admin", 11, 1, 1) ' adBoolean
For i = 0 To UBound(ids)
tt = Trim(ids(i))
If IsNumeric(tt) Then
cmd.Parameters("@id").Value = CLng(tt)
cmd.Parameters("@owner_id").Value = userId
cmd.Parameters("@is_admin").Value = isAdmin
cmd.Execute
If Err.Number <> 0 Then Exit For
End If
Next
If Err.Number = 0 Then
conn.CommitTrans
Response.Redirect "house.asp?xxlb=" & xxlb & "&msg=deleted"
Else
conn.RollbackTrans
Response.Write "刪除失敗: " & Err.Description
End If
Set cmd = Nothing
conn.Close: Set conn = Nothing
%>
現代化遷移對照
| ASP 3.0 | ASP.NET Core / EF Core |
| `conn.Execute "DELETE ..."` | `await db.Houses.Where(h => ids.Contains(h.Id) && (h.OwnerId == userId || isAdmin)).ExecuteDeleteAsync();` |
| `Server.MapPath` | `IWebHostEnvironment.ContentRootPath` + `Path.Combine` |
| `Session("UserID")` | `User.FindFirst(ClaimTypes.NameIdentifier).Value` |
| `Response.Redirect` | `return RedirectToAction("Index", new { xxlb });` |
核心原則:
1. **永不拼接 SQL** — 全站強制參數化查詢 (`Command.Parameters`)
2. **連線物件區域化** — 每個請求建立/關閉自己的連線,勿用全域變數
3. **授權檢查前置** — 刪除前驗證擁有權或管理員身分
4. **交易包裹批次操作** — 全成功或全失敗,保持資料一致性
5. **錯誤不外洩** — 生產環境關閉詳細錯誤,記錄 Log 供開發者追查