Sep 29

ASP.NET Padding Oracle漏洞修复补丁代码分析

这几天ASP.NET的Padding Oracle漏洞视频)闹得是沸沸扬扬,M$刚刚发布了修复补丁,EP起来于是研究下M$是怎么样修复的。

Reflector跟踪一下,很容易能找到关键方法是System.Web.Configuration.MachineKeySection.EncryptOrDecryptData,对比补丁前和补丁后的方法主体发现补丁后的版本主要是添加了Hash签名检测:

   1: // ... snip ...

   2:  

   3: if (!fEncrypt && signData)

   4: {

   5:     if ((start != 0) || (length != buf.Length))

   6:     {

   7:         byte[] dst = new byte[length];

   8:         Buffer.BlockCopy(buf, start, dst, 0, length);

   9:         buf = dst;

  10:         start = 0;

  11:     }

  12:     buf = GetUnHashedData(buf);

  13:     if (buf == null)

  14:     {

  15:         throw new HttpException(SR.GetString("Unable_to_validate_data"));

  16:     }

  17:     length = buf.Length;

  18: }

  19: // ... snip ...

(hash函数内部使用validationKey作为salt或者HMAC的key,这里略过)

另外整个方法体也被一个try catch块包了起来,遇到异常直接抛出统一的异常:

   1: try

   2: {

   3:     // ... snip ...

   4: }

   5: catch

   6: {

   7:     throw new HttpException(SR.GetString("Unable_to_validate_data"));

   8: }

从这两点看来,理论上Padding Oracle攻击的漏洞已经被堵上了。首先,因为加上了数据签名,攻击者没办法随意修改已知的数据;另外抛出的异常内部把有关错误原因的信息都消除了,攻击者没办法区分Padding错误和解密错误,攻击自然没办法进行了。

M$这次的反应速度算是不错,漏洞爆出来两个星期左右就放出补丁了,估计是因为影响太大了吧。

Sep 10

神奇的Boolean

   1:  

   2: // "true" or "false"

   3: public static string Bool2Str(bool b)

   4: {

   5:     switch (b)

   6:     {

   7:         case true:

   8:         return System.Boolean.TrueString;

   9:         case false:

  10:         return System.Boolean.FalseString;

  11:         default:

  12:         return "error";

  13:     }

  14: } 

  15:  

  16: Console.WriteLine(Bool2Str(true));

  17: Console.WriteLine(Bool2Str(false)); 

  18: unsafe

  19: {

  20:     bool val;

  21:     *((byte*)&val) = 2;

  22:     Console.WriteLine(val); 

  23:     Console.WriteLine(val==true); 

  24:     Console.WriteLine(Bool2Str(val));

  25: }

  26:  

大家可以猜一下这段代码会输出什么,我也是看了某篇文章的评论才知道会出现这么诡异的情况。。。

Jun 30

VB2010和C#4.0后期绑定的实现比较

昨天EP研究了一下C#4.0和VB2010的后期绑定实现,弄了两个简单的测试程序,反编译出来代码如下:

   1: <STAThread> _

   2: Public Shared Sub Main()

   3:     Dim o As Object = New ExpandoObject

   4:     NewLateBinding.LateCall(o, Nothing, "Test", New Object(0  - 1) {}, Nothing, Nothing, Nothing, True)

   5:     NewLateBinding.LateSet(o, Nothing, "NewMember", New Object() { 1 }, Nothing, Nothing)

   6: End Sub

   1: private static void Main(string[] args)

   2: {

   3:     object o = new ExpandoObject();

   4:     if (<Main>o__SiteContainer0.<>p__Site1 == null)

   5:     {

   6:         <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Test", null, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));

   7:     }

   8:     <Main>o__SiteContainer0.<>p__Site1.Target.Invoke(<Main>o__SiteContainer0.<>p__Site1, o);

   9:     if (<Main>o__SiteContainer0.<>p__Site2 == null)

  10:     {

  11:         <Main>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, int, object>>.Create(Binder.SetMember(CSharpBinderFlags.None, "NewMember", typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant | CSharpArgumentInfoFlags.UseCompileTimeType, null) }));

  12:     }

  13:     <Main>o__SiteContainer0.<>p__Site2.Target.Invoke(<Main>o__SiteContainer0.<>p__Site2, o, 1);

  14: }

C#会给调用生成一个CallSite缓存起来,然后后续调用全部是通过这个CallSite。VB 则是全部通过 NewLateBinding 来调用,而且内部每次调用都会新建一个CallSite。

开始还以为M$脑子进水了,VB的后期绑定这样弄性能得差到一个什么程度。后来跟进CallSite的代码,发现其实CallSite里面也是有缓存机制的,内部的Delegate不会重复建立,这样的话虽然VB的性能会比C#稍差,应该也不到不能接受的地步。

这么做应该是为了兼容旧的VB6后期绑定,从代码也可以看出来,如果对象没有实现IDynamicMetaObjectProvider,就会用旧版的后期绑定方式。

这样保持兼容性感觉很不方便,VB.NET相比VB6的一大改进是Option Strict,之前写代码的时候一直开着,能减少很多编码错误,其中包括误用后期绑定。而如果要使用新的ExpendoObject和其它动态功能的话,又不能启用这个检查。

另外VB不像C#新增了dynamic关键字,要使用后期绑定得把变量声明为Object类型,使代码更容易出错。

结论:在后期绑定这方面,VB本来是前辈,不过现在反而被C#赶上了。。。该说是VB的又一个悲剧么。。。

(.NET内部的反编译代码就不贴了,各位有兴趣可以用Reflector研究一下)

——————————————–

7月11日更新:

早上突然发现上次的推理有个大漏洞,VB的NewLateBinding只会对IDynamicMetaObjectProvider使用CallSite,一般对象还是会使用老版的反射调用。这样一来多次调用的性能就远远比不上C#了。。。

Mar 24

gcc编译基本要点

弄了一上午终于开始make gcc了。。。于是记录一下要点。。。

0、在/etc/profile加上以下设置可减少很多麻烦事:

export CFLAGS='-I/usr/local/include'

export CPPFLAGS='-I/usr/local/include'

export LDFLAGS='-L/usr/local/lib'

1、必须库及编译顺序:gmp –> mpfr –> polylib –> ppl –> cloog-ppl

2、必须库configure的时候全部用–enable-shared –disable-static

特例:

ppl:需要加上LDFLAGS=’-no-undefined -L/usr/local/lib’,否则编译出来的还是静态库,后面编译cloog-ppl的时候会出现一大堆undefined reference。这个错误折磨了我很久。。。

cloog-ppl:需加上–with-ppl=/usr/local/,这个是编译gcc的要求

3、必须在gcc源代码目录树的外面新建一个文件夹,并在里面configure及make(请允许我吐槽。。。这是什么鬼规定。。。

3-26更新:

(前面的写完之后又折腾了一整天,编译gcc果然非常ep。。。

4、mingw目录必须在分区根部(如D:\MingW),不然会出现很奇怪的问题。。。

5、gcc在configure的时候要显式指定1里面4个库的位置,否则stage2会出错

6、cloog-ppl也要加上ppl的ldflags,不加的话编译出来的还是静态库

7、编译libgomp的时候有可能会出现ar.exe的错误,把其它目录的libtool复制到libgomp里面覆盖掉继续make就可以。这个操作可能要重复5次以上。。。

大概就是这样了。。。另外感谢squallatf同学的大力协助,很多问题都是他解答的,让我少走很多弯路。。。

Mar 12

Entity Framework CompiledQuery某bug及workaround

最近的项目里面用到了EF,某个功能需要做大量查询,性能很糟糕。

于是尝试把查询改成CompiledQuery,结果发现使用后性能反而下降了。祭出Profiler,结果发现:

image

有1/3的查询都调用了System.Data.Metadata.Edm.ObjectItemCollection.LoadAllReferencedAssemblies这个方法,时间基本全部耗在上面了。

看名字猜测,这方法似乎是用来加载查询引用的程序集的,但是理论上程序集只需要加载一次而已,感觉是EF内部的缓存机制出了问题。

Reflector反编译之,发现以下代码段:

   1: ' System.Data.Objects.CompiledQuery

   2: Private Function Invoke(Of TArg0 As ObjectContext, TArg1, TArg2, TArg3, TResult)(ByVal arg0 As TArg0, ByVal arg1 As TArg1, ByVal arg2 As TArg2, ByVal arg3 As TArg3) As TResult

   3:     EntityUtil.CheckArgumentNull(Of TArg0)(arg0, "arg0")

   4:     arg0.MetadataWorkspace.LoadAssemblyForType(GetType(TResult), Assembly.GetCallingAssembly)

   5:     Return Me.ExecuteQuery(Of TResult)(arg0, New Object() { arg1, arg2, arg3 })

   6: End Function

   7:  

   8: ' System.Data.Metadata.Edm.MetadataWorkspace

   9: Friend Sub LoadAssemblyForType(ByVal type As Type, ByVal callingAssembly As Assembly)

  10:     Dim items As ItemCollection

  11:     If Me.TryGetItemCollection(DataSpace.OSpace, items) Then

  12:         Dim items2 As ObjectItemCollection = DirectCast(items, ObjectItemCollection)

  13:         If (Not items2.LoadAssemblyForType(type) AndAlso (Not callingAssembly Is Nothing)) Then

  14:             items2.LoadAllReferencedAssemblies(callingAssembly)

  15:         End If

  16:     End If

  17: End Sub

  18:  

  19: ' System.Data.Metadata.Edm.ObjectItemCollection

  20: Friend Function LoadAssemblyForType(ByVal type As Type) As Boolean

  21:     Dim flag As Boolean = ObjectItemCollection.LoadAssemblyFromCache(Me, type.Assembly, False)

  22:     If type.IsGenericType Then

  23:         Dim type2 As Type

  24:         For Each type2 In type.GetGenericArguments

  25:             flag = (flag Or Me.LoadAssemblyForType(type2))

  26:         Next

  27:     End If

  28:     Return flag

  29: End Function

  30:  

  31:  

  32:  

  33: Private Shared Function LoadAssemblyFromCache(ByVal objectItemCollection As ObjectItemCollection, ByVal [assembly] As Assembly, ByVal loadReferencedAssemblies As Boolean) As Boolean

  34:     Dim flag As Boolean

  35:     If ObjectItemCollection.ShouldFilterAssembly([assembly].FullName) Then

  36:         Return False

  37:     End If

  38:     

  39:     ' skipped

  40: End Function

  41:  

  42: Friend Shared Function ShouldFilterAssembly(ByVal fullName As String) As Boolean

  43:     Return fullName.EndsWith("PublicKeyToken=b77a5c561934e089", StringComparison.OrdinalIgnoreCase)

  44: End Function

 

b77a5c561934e089是M$程序集的PublicKeyToken,看到这里基本就明白了。CompiledQuery执行的时候会加载调用方程序集引用的所有程序集,以TResult及其泛型参数作为键来缓存(具体的流程没有深入研究,可能不太准确)。在取缓存的时候如果键类型是M$程序集里面的话就会直接跳过。

但是取缓存的代码有个bug,如果TResult和它的泛型参数全部是M$程序集里面的类型的话,LoadAssemblyForType就会返回False,导致每次查询都会调用LoadAllReferencedAssemblies,大幅拖慢速度。

项目里面的符合条件的查询有2个,刚好是1/3,分别是返回IQueryable(Of Integer)和IQueryable(Of Decimal)。知道原因之后解决方法就很简单了。新建一个容器类如PrimitiveHolder(Of T),然后把CompiledQuery里面的TResult改成容器类就可以了,如IQueryable(Of PrimitiveHolder(Of Integer))和IQueryable(Of PrimitiveHolder(Of Decimal))。

(话说这几天弄项目一直在捣鼓EF,发现问题真的很多。。。果然M$的东西第一个版本都是这样么。。。希望.Net 4.0的EF会有改进。。。

Mar 04

小技巧:在VB中使用Module模拟C#的静态类

VB中建立助手类一般有两种方法,一是使用Module,另一个是用Class然后把成员全部声明为Shared。

不过这两种方法都有缺点,第一种方法由于VB的特性会污染命名空间;第二个需要显式声明Shared,如果以后要把代码转换到实例方法会很麻烦。(还有个问题是我经常忘记加,在调用成员失败的时候才想起来。。。)

最近发现了一个很简单的方法,能把两者的优点结合起来,范例:

   1:  

   2: Namespace Utils

   3:     <HideModuleName()> _

   4:     Module Utils

   5:         Public Sub XXX()

   6:             ' ...

   7:         End Sub

   8:     End Module

   9: End Module

  10:  

  11: ' 使用方法时Import ExcelUtils的上层命名空间即可

  12: Utils.XXX()

HideModuleName属性使VS的Intellisense在显示Utils命名空间的时候不显示类名,这样这个命名空间里面就只有Utils类的成员了,间接实现了静态类的效果。

Feb 07

Silverlight3小技巧:从Binding获取值

BlendSDK里面的交互模式很好用,不过交互的类全部从DependencyObject继承,没办法绑定很不方便。于是就弄了这个类出来:

   1: Public NotInheritable Class BindingEvaluator(Of T)

   2:     Private Shared ReadOnly DummyProperty As DependencyProperty = DependencyProperty.RegisterAttached("Dummy", GetType(T), GetType(BindingEvaluator(Of T)), New PropertyMetadata(DependencyProperty.UnsetValue))

   3:  

   4:     Private Shared Function GetDummy(ByVal element As DependencyObject) As T

   5:         ' IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!

   6:         If (element Is Nothing) Then

   7:             Throw New ArgumentNullException("element")

   8:         End If

   9:         Return CType(element.GetValue(BindingEvaluator(Of T).DummyProperty), T)

  10:     End Function

  11:     Private Shared Sub SetDummy(ByVal element As DependencyObject, ByVal value As T)

  12:         ' IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!

  13:         If (element Is Nothing) Then

  14:             Throw New ArgumentNullException("element")

  15:         End If

  16:         element.SetValue(BindingEvaluator(Of T).DummyProperty, value)

  17:     End Sub

  18:  

  19:     Public Shared Function GetValue(ByVal source As FrameworkElement, ByVal binding As Binding) As T

  20:         Dim value = GetRawValue(source, binding)

  21:         If value Is DependencyProperty.UnsetValue Then

  22:             Return Nothing

  23:         End If

  24:         Return CType(value, T)

  25:     End Function

  26:  

  27:     Public Shared Function GetRawValue(ByVal source As FrameworkElement, ByVal binding As Binding) As Object

  28:         If source Is Nothing Then

  29:             Throw New ArgumentNullException("source", "source is nothing.")

  30:         End If

  31:         If binding Is Nothing Then

  32:             Throw New ArgumentNullException("binding", "binding is nothing.")

  33:         End If

  34:         Dim newBinding As New Binding() With { _

  35:             .BindsDirectlyToSource = binding.BindsDirectlyToSource, _

  36:             .Converter = binding.Converter, _

  37:             .ConverterCulture = binding.ConverterCulture, _

  38:             .ConverterParameter = binding.ConverterParameter, _

  39:             .Mode = BindingMode.OneTime, _

  40:             .Path = binding.Path _

  41:         }

  42:         If Not String.IsNullOrEmpty(binding.ElementName) Then

  43:             newBinding.ElementName = binding.ElementName

  44:         ElseIf binding.RelativeSource IsNot Nothing Then

  45:             newBinding.RelativeSource = binding.RelativeSource

  46:         ElseIf binding.Source IsNot Nothing Then

  47:             newBinding.Source = binding.Source

  48:         Else

  49:             '    newBinding.Source = source.DataContext

  50:         End If

  51:  

  52:         source.SetBinding(DummyProperty, newBinding)

  53:         Dim value = GetDummy(source)

  54:         source.ClearValue(DummyProperty)

  55:  

  56:         Return value

  57:     End Function

  58:  

  59:     Private Sub New()

  60:  

  61:     End Sub

  62: End Class

SL4就要支持DependencyObject绑定了,期待。。。