要求Reflection API覆盖System.String.Empty的含义是什么?

时间:2011-06-09 13:56:50

标签: c# .net reflection

我偶然发现了这段代码:

static void Main()
{
    typeof(string).GetField("Empty").SetValue(null, "evil");//from DailyWTF

    Console.WriteLine(String.Empty);//check

    //how does it behave?
    if ("evil" == String.Empty) Console.WriteLine("equal"); 

    //output: 
    //evil 
    //equal

 }

我想知道如何编译这段代码甚至是可能的。我的理由是:

根据MSDN String.Empty是只读的,因此更改它应该是不可能的,并且编译应以“无法分配静态只读字段”或类似错误结束。

我认为Base Class Library程序集以某种方式受到保护和签名,以及什么可以防止这种攻击。下次有人可能会更改System.Security.Cryptography或其他关键类。

我认为基类库程序集是在安装.NET之后由NGEN编译的,因此更改String类的字段应该需要高级黑客并且要困难得多。

然而这段代码编译并运行。有人可以解释我的推理有什么问题吗?

5 个答案:

答案 0 :(得分:52)

  

无法将静态只读字段分配给

你没有分配给它。您正在System.Reflection命名空间中调用公共函数。没有理由让编译器抱怨。

此外,typeof(string).GetField("Empty")可以使用用户输入的变量,而编译器无法确定所有情况下GetField的参数是否最终为"Empty"

我认为您希望Reflection看到该字段标记为initonly并在运行时抛出错误。我可以看到为什么你会这么想,但对于白盒测试,即使写入initonly字段也有一些应用。

NGEN没有效果的原因是你不是在这里修改任何代码,只修改数据。与任何其他语言一样,数据与.NET一起存储在内存中。本机程序可以使用readonly内存部分来处理字符串常量之类的东西,但是指向字符串的指针通常仍然是可写的,这就是这里发生的事情。

请注意,您的代码必须以完全信任的方式运行才能以这种有问题的方式使用反射。此外,更改只影响一个程序,这不是您认为的任何类型的安全漏洞(如果您在流程中以完全信任的方式运行恶意代码,那么设计决策是安全问题,而不是反射)


进一步注意initonly内的mscorlib.dll字段的值是.NET运行时的全局不变量。打破它们之后,你甚至无法可靠地测试不变量是否被破坏,因为检查System.String.Empty的当前值的代码也已经破坏,因为你违反了它的不变量。开始违反系统不变量,不能依赖任何东西。

通过在.NET规范中指定这些值,它使编译器能够实现一大堆性能优化。只是一个简单的就是那个

s == System.String.Empty

(s != null) && (s.Length == 0)

是等价的,但后者要快得多(相对而言)。

编译器也可以确定

if (int.Parse(s) > int.MaxValue)

永远不会成立,并且会生成无条件跳转到else块(它仍然必须调用Int32.Parse才能具有相同的异常行为,但可以删除比较。)

System.String.Empty也在BCL实现中广泛使用。如果你覆盖它,可能会发生各种各样的疯狂事情,包括程序外泄漏的损坏(例如,您可能会写入一个名称使用字符串操作构建的文件...当字符串断开时,你可能会覆盖错误的文件)


.NET版本之间的行为可能很容易不同。通常,当找到新的优化机会时,它们不会被反向移植到以前版本的JIT编译器(即使它们是,也可能在实现backport之前进行安装)。特别是。 String.Empty - 相关的优化在.NET 2.x和Mono以及.NET 4.5 +之间明显不同。

答案 1 :(得分:42)

代码编译,因为代码的每一行都是完全合法的C#。您认为哪条特定行应该是语法错误?那里没有代码行分配给只读字段。有一行代码调用Reflection中的一个方法,该方法分配给一个只读字段,但是已经编译了,最终破坏安全性的东西甚至没有用C#编写,它是用C ++编写的。它是运行时引擎本身的一部分。

代码成功运行,因为完全信任意味着完整信任。您在完全信任环境中运行代码,并且由于完全信任意味着完全信任,运行时假设您在执行此愚蠢危险时知道自己在做什么。

如果您尝试在部分受信任的环境中运行代码,那么您将看到Reflection抛出“您不被允许这样做”的异常。

是的,这些集会已经签署了诸如此类的东西。如果您正在运行完全信任的代码,那么他们可以根据需要随意使用这些程序集。 这就是完全信任的含义。部分受信任的代码无法做到这一点,但完全受信任的代码可以做任何你能做的事情。 仅对您实际信任的代码授予完全信任,不代表您做疯狂的事情。

答案 2 :(得分:20)

反射允许你违反物理定律做任何事情。您甚至可以设置私有成员的值。

反思不遵循规则,you can read about it on MSDN

另一个例子:Can I change a private readonly field in C# using reflection?


如果您使用的是Web应用程序,则可以设置trust level of your application.

level="[Full|High|Medium|Low|Minimal]"

These are the restriction of the trust level, accondingly to MSDN,在中等信任下,您限制反射访问。

编辑:不要运行完全信任以外的Web应用程序,这是ASP.NET团队的直接建议。为保护您的应用程序,请为每个网站创建一个应用程序池。


此外,不建议使用反射来解决所有问题。它有正确的地点和时间使用。

答案 3 :(得分:5)

之前没有人提到这一点:这段代码在不同的.net实现/平台上引起不同的行为。实际上在Mono上它什么都没有返回:see IDE One (Mono 2.8),我的本地Mono 2.6.7(linux)产生相同的“输出”。

我还没有看过低级代码,但我认为它是编译器特定的,正如PRASHANT P或运行时环境所提到的那样。

<强>更新

在Windows上运行Mono编译的exe(MS Dotnet 4)会产生

evil 
equal

在Linux上运行Windows编译的exe是不可能的(Dotnet 4 ...)所以我用Dotnet 2重新编译(它仍然在Windows上说 evil 相等) 。有“无”输出。当然,第一个"\n"必须至少有WriteLine,事实上它就在那里。我将输出传送到文件并启动我的hexeditor以查看单个字符0x0A

简而言之:它似乎是运行时环境特定的。

答案 4 :(得分:0)

readonly只是强制执行

在COMPILER级别

因此

可能会在当前水平下更改