近日參與古蹟翻修,遇到第一個問題是 Web Site Project 網站仍在使用過時的 System.Data.OracleClient,出現之前交手過的中文變問號狀況,加上它有效能不佳的前科,心一橫決定把它改寫成 ODP.NET。(原想直上 Managed ODP.NET,但系統仍依賴用 Unmanged ODP.NET 的程式庫,為控制施工範圍不要擴散,先用 Unmanaged ODP.NET)

從 System.Data.OracleClient 改 ODP.NET 不難,改 using 命名空間,只改掉幾個 AddWithValue() 還有 OracleType 換 OracleDbType,程式改動不大。但執行後我很快踩到 BindByName 的雷。System.Data.OracleClient 是用變數名稱繫結參數,ODP.NET 預設依參數順序,需指定 OracleCommand.BindByName = true 才行。我有兩種選擇,一個是地毯式清查所有 OracleCommand 補上 BindByName = true (古老程式沒模組化,OracleConnection、OracleCommand 遍地開花,令人頭皮發麻);第二條路是設法找出改掉預設值的方法。懶惰如我,當然選後者。

爬文得到壞消息,ODP.NET 不提供修改 BindByName 預設值的方法,倒是 Managed ODP.NET 可透過 config 指定

<oracle.manageddataaccess.client>
<version number="*">
  <settings>
    <setting name="BindByName" value="True"/>
  </settings>
</version>
</oracle.manageddataaccess.client>

OracleCommand 不開放繼承,有網友想出自訂 Command 類別包住 OracleCommand 再偷改 BindByName,但應用起來要動的地方挺多,我還是覺得直接改掉預設值才是最乾淨俐落的做法。ODP.NET 不提供修改預設值的管道,我們就束手無策了嗎?哼! 那要先問問老朽的鍵盤答不答應,立馬開啟駭客模式。

專案使用的 ODP.NET 版本是 2.122.1.0,用 JustDecompile 反組譯查到 OracleCommand.BindByValue 的預設值來自 ConfigBaseClass.m_BindByName:

這是個好消息,代表每次建立 OracleCommand 物件時會由 ConfigBaseClass 讀取靜態欄位 m_BindByName 當成預設值,若能把它改成 true,之後再建立 OracleCommand,BindByName 預設也會變成 true。小問題是 ConfigBaseClass 被宣告成 internal,m_BindByName 也是 internal 只限 ODP.NET 自己的型別存取,代表從我們的專案摸不到 ConfigBaseClass 也改不了 m_BindByName,除非... 使用 System.Reflection:

using System.Reflection;

public class OdpNetPatch
{
    public static void SetBindByNameByDefault()
    {
        var t = typeof(Oracle.DataAccess.Client.OracleCommand)
            .Assembly.GetType("OracleInternal.Common.ConfigBaseClass");
        var f = t.GetField("m_BindByName", BindingFlags.Static | BindingFlags.NonPublic);
        f.SetValue(null, true);
    }
}

如此,在 Global.asax 安排程序啟動時呼叫 OdpNetPath.SetBindByNameByDefault(),之後在網站建立的 OracleCommand.BindByName 預設都會是 true。為了驗證,在呼叫前後分別 new 一個 OracleCommand 對照,如下圖所以,證明修補前 BindByName 為 false,修補後為 true,駭客任務順利完成。

補充:以上修改方式僅在 4.122.1.0 / 2.122.1.0 實測過,ODP.NET X.121.1.0 舊版則因寫法不同,未使用 ConfigBaseClass 統一預設值,無法套用這招,但目前專案確定是用 2.122.1.0,就不傷腦筋了。

Little trick to set OracleCommand.BindByName to true by default via System.Reflection.


Comments

# by Slash

利用Reflection好妙計!小勘誤:Global.asa 少了 x。

# by Jeffrey

to Slash, 感謝指正,已修改。

# by Tim

這真的太厲害

# by Andy.Hsu

本來因為同仁的迷音不太敢在正式專案使用, 都忘記還有這招破解底層限制的大招了! 感謝 Jeffrey 讓我在睡前找到靈感~ 成功破解HttpListener 應用在comet會強制變成Chucked encoding.

# by Mark

請問有沒有人遇到使用managed,但在config設定BindByName卻沒作用的狀況?

Post a comment