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

Kry*_*lik 61 .net c# 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

 }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

然而,这段代码编译和工作.有人可以解释我的推理有什么问题吗?

Ben*_*igt 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
Run Code Online (Sandbox Code Playgroud)

(s != null) && (s.Length == 0)
Run Code Online (Sandbox Code Playgroud)

是相同的,但后者更快(相对而言).

编译器也可以确定

if (int.Parse(s) > int.MaxValue)
Run Code Online (Sandbox Code Playgroud)

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

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


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

  • @Krycklik:如果您有"软件的另一部分"并且不信任开发人员,请使用部分信任加载该子模块.你在谈论给一个邪恶的开发者无限制的权利,不要这样做.使用.NET"代码访问安全性"仅提供执行任务所需的权限. (5认同)
  • @Krycklik:如果你不相信你的开发者,游戏已经结束了.强制性XKCD参考:http://www.xkcd.com/898/ (5认同)
  • @Krycklik:.NET安全系统并非旨在以任何方式防御敌对的完全信任的开发人员.安全系统旨在保护*用户*免受恶意开发人员的攻击,这些开发人员编写用户随后从互联网上下载的代码*; 也就是说,"部分信任"的代码场景.如果你有恶意的开发人员可以编写代码并让用户以完全信任的方式运行它,那么你将不得不想出一些过程来检测和起诉它们; .NET运行时表示完全信任是完全信任. (5认同)
  • @Krycklik:因为听起来你担心来自不同代码提供者的代码互相攻击,你应该知道另一件事.在.NET 4安全模型中,*允许所有相同信任级别的部分信任代码在彼此的内部*上进行聚合.也就是说,如果您加载部分受信任的Water.DLL和部分受信任的Wine.DLL以及完全信任的Whiskey.DLL,那么Water和Wine可能会混淆彼此的内部属性.威士忌可以搞砸三者的内在属性.水和酒不能与威士忌混淆. (3认同)

Eri*_*ert 42

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

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

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

是的,组件已签名,以及诸如此类的东西.如果您正在运行完全信任的代码,那么他们可以根据需要随意使用这些程序集.这就是完全信任的含义.部分受信任的代码无法做到这一点,但完全受信任的代码可以做任何你能做的事情.只对您真正信任的代码授予完全信任,才能代表您做疯狂的事情.

  • 我反对说这个"破坏安全性",看[Raymond Chen的优秀"它更多地涉及在这个密不透风的舱口的另一边"文章](http://www.google.com/search?q=oldnewthing+airtight) .其他我同意的其他100%. (3认同)

Bru*_*oLM 20

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

反射不遵循规则,您可以在MSDN上阅读它.

另一个例子:我可以使用反射更改C#中的私有只读字段吗?


如果您使用的是Web应用程序,则可以设置应用程序的信任级别.

level="[Full|High|Medium|Low|Minimal]"
Run Code Online (Sandbox Code Playgroud)

这些是信任级别的限制,与MSDN相关,在中等信任下,您限制反射访问.

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


此外,不建议使用反射来解决所有问题.它有合适的地方和时间使用.

  • @Krycklik:没有安全问题,因为你有一个*完全信任*代码攻击其他*完全信任*代码.谁在乎?攻击代码完全可信; 如果它是敌对的,它可能已经伤害了用户,但它想要的.如果你有充满敌意的完全信任的代码,那么*它已经赢了.*如果*部分信任的*代码可以拉这样的恶作剧,它只有一个安全问题,它不能. (16认同)
  • 有反射绕过(可访问性)和反射强制执行的规则(类型安全)的规则.为什么`initonly`应该属于第一类是一个很好的问题. (2认同)

mbx*_*mbx 5

前面没有提到的一点:这段代码在不同的.net实现/平台上导致不同的行为.实际上在Mono上它什么都没有返回:参见IDE One(Mono 2.8),我的本地Mono 2.6.7(linux)产生相同的"输出".

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

UPDATE

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

evil 
equal
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我猜JIT优化器在某些情况下可能出现特殊情况`String.Empty`,这是出于性能原因,而实际上并没有从字段中读取.例如,`s == String.Empty`可以用`s.Length == 0`代替,这更快.通过使代码稍微复杂一点,我已经击败了JIT的模式识别并避免了特殊处理.但这只是假设. (4认同)