cmd.exeparsing错误导致其他利用?

在我继续之前,请注意,这是一个相当长的关于Windows命令提示符的infosec通知,因为我发现了一个可能被使用简单的batch file利用的bug。 这个bug在2000以后的所有Windows版本中都很普遍,在64位和32位机器上都能正常运行,而且这是一个batch fileparsing错误,它不需要额外的软件来安装( cmd.exe是Windows的缺省部分)并且可以由任何具有任何级别的特权的用户启动(假设他们可以运行cmd.exe并因此parsingbatch file)。 在这个总结中包含了错误发生的地方(用代码stream来分析原因)。 这不是一个RCE级的bug(不是我已经能够find的),只是一个DoStypes,并且需要用户运行它(或者把它作为启动项),但是考虑到它的简单性和Windows系统无处不在,我觉得值得再看一下。 请注意,如果您运行任何这些启用batch file的bug并导致系统崩溃(任务pipe理器并在运行失控脚本的情况下终止PID),则我不承担任何责任。

tldr :只有这行^ nul<^的batch file会导致大量的内存泄漏,而只有这一行的batch file会导致命令提示符因“无限recursion”而崩溃。 这些行为可能导致在任何Windows系统(Win2k +)上发生有趣的批量“黑客攻击”,原因是cmd.exebatch file处理中存在逻辑错误(请参阅下面的汇编和伪C代码以获取更多信息)。

背景

在回答关于超级用户( 链接 )的问题时,我遇到了一个有趣的exception情况:命令解释器如何parsingbatch file。 如果脱字符( ^ )是该文件的最后一个字符,则可能会发生内存泄漏/ cmd.exe崩溃。 脱字符必须是文件的最后一个字符,不能跟着\n (换行字符),尽pipe\r (回车符)是正确的,因为在脱字符被parsing之前它已被删除; 没有任何东西可以跟踪它,因为它会导致parsing器正常进行(因为^\r\t会变成“ ^\t因此\t被“忽略”)。 关于回车字符的最后一个注意事项,这个错误仍然发生,这实际上使它更有趣一些,因为它在大多数文本编辑器中“伪造”了一个换行符,而在记事本中,你可能会被愚弄到认为最后有一个换行符'技术上'这是一个回车或'旧的Mac'新线)。

在做了一些快速的调查之后,我发现文件末尾的^可能会导致内存泄漏或者可能导致命令提示符崩溃(更具体地说是command.com或cmd.exe程序),我还发现特定的batch file及其序列)可以导致一些非常有趣的行为。 进一步的调查使我向其他人提出类似的问题。 一个Stack Overflow问题,用户在ss64.com上发现了一个内存泄漏和一个留言板话题,这个话题logging了EOF插入符其他有趣的行为 。 堆栈溢出问题有助于确认暂停,这是一个无限循环types的情况,但没有试图潜水比这更深。 ss64.org主题大多讨论了各种使命令提示符崩溃的方法,但未能解释它是什么样的崩溃或原因。

这当然会让我质疑发生了什么,为什么(可以被利用)。 答案是混合的,修复很简单(至less从我正在检查的程序集看起来是“简单的”)。 我发现可以产生内存泄漏的batch file技巧的几种组合,多快或多慢取决于batch file中的内容(而不是多less插入符号,但pipe道顺序和有趣的行长度[稍后])并且无论崩溃或内存泄漏,parsing器代码在单个线程中处于紧密的循环中,因此CPU使用率显着增加(单核CPU或推送到单个关联,平均产生98%以上的CPU使用率)。

崩溃

要使命令提示符崩溃非常简单,没有换行符的batch file只包含以下字符^&^ (或^|^ ),将导致批处理命令提示符崩溃,错误代码为0xC00000FD ,这是由于无限recursion导致的堆栈/帧溢出。 这证实了'无限循环'的情况,但没有完全解释内存泄漏(或为什么因为无限的recursion而崩溃)。 在这个意义上,我开始研究一些最简单的产生内存泄漏的方法。 事实certificate,一个2字节的batch file是所有需要消耗100%的CPU和吃内存(尽pipe速度非常慢,在2.2GHz的i7约8k / 5s)。

吃记忆

我用来testing的batch file包含以下hex字节:

 0x01 0x5E 

0x5E是插入字符( ^ ), 0x01是“开始标头”的ASCII字符代码。 第一个HEX代码在这个bug中只有一半的意义,它不能为空( 0x00 ), \r| ,或者这些字符会导致batch file通过正常终止(即“检测到无效的命令”)退出。 我用0x01来certificate它不必是一个有效的命令(或可打印的字符)来诱发错误,一个2字节的batch file只包含a^就足够了。

做了一些其他的testing,我发现用batch file吃掉内存(最简单)的最快方法是以下行: ^ nul<^

…有几次无意中运行这个,请注意,这会使64位系统相当快地崩溃; 在我的四核(HTT,所以有8个有效内核)i7上,我花了大约20秒钟时间,全部剩余的14GB内存(总共16GB),这使得我的机器无用,而一切都在尝试页面(我必须重新启动机)。 在32位系统上运行这个“快速”版本,结束了命令parsing器out of memory不足错误,因为它很快耗尽了32位的2GB限制。 它不会崩溃的命令提示符,而是似乎有一个检查,以确保内存可以正确malloc D,当它不能它只是中止批处理。

链式的bug乐趣

请注意,虽然所有这些“批处理漏洞”可以“串连”在一起,所以在一个32位系统上(假设它只有4GB RAM,没有PAE),可以运行以下命令对其执行一个DoS: cmd eat_mem.bat | eat_mem.bat cmd eat_mem.bat | eat_mem.bat (这将启动2个命令parsing器,每个都cmd eat_mem.bat | eat_mem.bat 2GB)。

另一个有趣的变化是将“崩溃”batch file链接在一起; 例如,假设有一个名为crash.bat的文件,它包含我们上面( ^&^ )的'crash exploit',你可以这样做: cmd crash.bat | crash.bat cmd crash.bat | crash.bat并注意一些有趣的行为。 这个特定的导致cmd crash.bat的输出被写入到一个已经崩溃的pipe道(由于parsing器错误),因此当你试图从CTRL+C wrote to a non-existent pipe错误时, 有趣的是,虽然它仍然允许你执行命令,如打字notepad和打进入将启动记事本。 我已经探讨了更多的可能的pipe道劫持,但没有什么富有成果的(更多的“利用”下面)。

8K缓冲区

由于堆栈溢出问题/答案已经提到,越是你在一条线上,它会吃的内存越来越快(除了我发现的“快速”版本)。 通过一些更多的实验,我发现'行长'实际上对这个bug也有一些有趣的效果。 如果batch file的最后一行在结尾有一个^ ,并且长度正好是8192字节(包括插入符号),那么错误将会失败……然而,低于8192倍数的任何东西都会导致错误。 string中的字节越多,消耗的内存就越快(最高为8192的倍数)。 这个8192数字是有趣的,因为我们的内存泄漏是8K(或更大的块)。

应该注意的是,文件大小与错误无关,因此可以放在任何无害的batch file中。 作为一个testing,我把“快速”版本( ^ nul<^ )放在我用来编译大量遗留代码的batch file的末尾。 这个编译器脚本是在21KB以下,recursion调用和多个函数(为了让我编译某些东西),并在文件的末尾放置“错误的”行允许我像平常一样正常运行编译脚本,但是当它碰到“错误”时,它就会启动我的CPU,并迅速地将我的记忆。

如果我是一个毫不起眼的软件工程师,给了一个“简单的”编译器脚本(在那里随机调用:eof “强制”这个bug),如果脚本突然启动了我的CPU,我不会三思而后行,没有注意到我的任务pipe理器,我不明白我的内存吃多快,然后必须重新启动(不好玩)。

深入挖掘

从这里,我决定做一些更高级别的检查,看看发生这种泄漏事件时,命令提示符发生了什么。 我打开了Process Explorer和Procmon工具来检查进程(在PE堆栈上)和系统(通过Procmon)发生了什么。 Procmon用8191字节的缓冲区确认了8K(更具体地说是8191字节)的泄漏,并对ReadFile进行了调用,PE确认了对ReadFile的内核调用,但是我不太确定batch file被读取的原因。

有趣的是,这个bug还允许batch file在运行和重新分析时被修改(高达8k的“parsing”,换行符和所有)。 应该指出的是,内容可以修改,某些命令将被处理,但我注意到,除非有很多其他的命令(更具体地说是在最后^之前换行),定时也可以成功地发挥作用一个修改过的批量“黑客”虽然经常发生(即大多数情况下)。

这些批处理漏洞使我不知道是否可以通过编程来完成。 我的第一次尝试是一个简单的C程序,只是system("crash.bat"); 其中调用了包含^&^的batch file。 这启动了一个单独的cmd.exe进程,随后崩溃返回控制到我的程序(这是所有的预期)。 然后,我切换到简单的内存泄漏文件(包含只是a^ ),并再次运行该程序,这次cmd.exe运行,并开始旋转起来(如预期),但是当我按CTRL+Ccmd.exe进程是孤儿,控制返回到我的程序,让我不得不通过任务pipe理器结束stream氓cmd.exe进程。 我也尝试直接在我的程序中使用从文件中的行(即调用一些system("^ nul<^");的效果system("^ nul<^"); )但有效的结果回(在这种情况下有效的意思是命令被认为是“无效的”由命令parsing器)。 我尝试了如何挂钩进程或利用堆栈/框架溢出错误的变体,但由于cmd.exe本身就是parsing器,所以一些更明显的漏洞利用将不会那么容易。 在这个意义上,我决定打开cmd.exe进程并检查程序集,以确认发生了什么泄漏,并更好地了解发生了什么以及如何利用它(如果有的话)。

代码(cmd.exe)

我使用Visual Studio 2010并附加到正在运行的cmd.exe进程,然后使用“简单的”内存泄漏batch file( a^ )暂停了该进程,以便在parsing器处于错误状态时通过程序集。 在检查程序集并使用Process Explorervalidationdebugging期间我在哪个模块之后,我能够跟踪大量代码stream,并findparsing器查找^字符(hex0x5E )的位置。 以下是汇编stream程,我可以看到问题在哪里。

请注意,很多“main”和“parsing”函数程序集已经被删除了(相关的代码发布,但我已经find的完整转储可以应要求提供[1.2 MB ASM dump with comments]):

 ; start cmd.exe asm code flow 000000004A161D50 ; { start main (more init frame/code here) ; { start loop 000000004A161FC1 3B F5 cmp esi,ebp ; mem leak here?? esi == #bytes, ebp == our 8191 buffer size number 000000004A161FC3 7D 29 jge 000000004A161FEE 000000004A161FC5 0F B7 44 24 20 movzx eax,word ptr [rsp+20h] 000000004A161FCA 48 8D 54 24 70 lea rdx,[rsp+70h] 000000004A161FCF 48 8D 4C 24 20 lea rcx,[rsp+20h] 000000004A161FD4 66 89 03 mov word ptr [rbx],ax 000000004A161FD7 48 83 C3 02 add rbx,2 000000004A161FDB FF C6 inc esi 000000004A161FDD 48 89 5C 24 60 mov qword ptr [rsp+60h],rbx 000000004A161FE2 E8 99 04 00 00 call 000000004A162480 ; main_parser_fn 000000004A161FE7 3D 00 01 00 00 cmp eax,100h 000000004A161FEC 75 D3 jne 000000004A161FC1 ; } end loop 000000004A162058 ; } end main?? function here ; { start main_parser_fn 000000004A162480 48 8B C4 mov rax,rsp 000000004A162483 48 89 58 08 mov qword ptr [rax+8],rbx 000000004A162487 48 89 70 10 mov qword ptr [rax+10h],rsi 000000004A16248B 48 89 78 18 mov qword ptr [rax+18h],rdi 000000004A16248F 4C 89 60 20 mov qword ptr [rax+20h],r12 000000004A162493 41 55 push r13 000000004A162495 48 83 EC 20 sub rsp,20h 000000004A162499 48 8B DA mov rbx,rdx 000000004A16249C 48 8B F9 mov rdi,rcx 000000004A16249F E8 BC FB FF FF call 000000004A162060 ; get_next_char 000000004A1624A4 33 F6 xor esi,esi 000000004A1624A6 66 89 07 mov word ptr [rdi],ax 000000004A1624A9 39 35 D1 98 03 00 cmp dword ptr [4A19BD80h],esi 000000004A1624AF 0F 85 F1 75 01 00 jne 000000004A179AA6 000000004A1624B5 0F B7 17 movzx edx,word ptr [rdi] 000000004A1624B8 41 BD 3C 00 00 00 mov r13d,3Ch 000000004A1624BE 8B CA mov ecx,edx 000000004A1624C0 45 8D 65 CE lea r12d,[r13-32h] 000000004A1624C4 3B D6 cmp edx,esi 000000004A1624C6 74 98 je 000000004A162460 000000004A1624C8 41 2B CC sub ecx,r12d 000000004A1624CB 74 93 je 000000004A162460 000000004A1624CD 83 E9 1C sub ecx,1Ch 000000004A1624D0 74 91 je 000000004A162463 000000004A1624D2 83 E9 02 sub ecx,2 000000004A1624D5 0F 84 61 FF FF FF je 000000004A16243C 000000004A1624DB 83 E9 01 sub ecx,1 000000004A1624DE 0F 84 6A FF FF FF je 000000004A16244E 000000004A1624E4 83 E9 13 sub ecx,13h 000000004A1624E7 0F 84 76 FF FF FF je 000000004A162463 000000004A1624ED 83 E9 02 sub ecx,2 000000004A1624F0 0F 84 6D FF FF FF je 000000004A162463 000000004A1624F6 83 E9 02 sub ecx,2 000000004A1624F9 0F 84 28 FF FF FF je 000000004A162427 ; quote_parse_fn 000000004A1624FF 41 3B CD cmp ecx,r13d ; check if it's the '<' 000000004A162502 0F 84 5B FF FF FF je 000000004A162463 000000004A162508 83 FA 5E cmp edx,5Eh ; start the '^' parse 000000004A16250B 0F 84 86 DC 00 00 je 000000004A170197 ; caret_parse 000000004A162511 83 FA 22 cmp edx,22h 000000004A162514 0F 84 5E 2F 00 00 je 000000004A165478 000000004A16251A F6 03 23 test byte ptr [rbx],23h 000000004A16251D 0F 84 E5 00 00 00 je 000000004A162608 000000004A162523 0F B7 0F movzx ecx,word ptr [rdi] 000000004A162526 FF 15 54 6C 02 00 call qword ptr [4A189180h] 000000004A16252C 3B C6 cmp eax,esi 000000004A16252E 0F 85 C0 10 00 00 jne 000000004A1635F4 000000004A162534 33 C0 xor eax,eax 000000004A162536 48 8B 5C 24 30 mov rbx,qword ptr [rsp+30h] 000000004A16253B 48 8B 74 24 38 mov rsi,qword ptr [rsp+38h] 000000004A162540 48 8B 7C 24 40 mov rdi,qword ptr [rsp+40h] 000000004A162545 4C 8B 64 24 48 mov r12,qword ptr [rsp+48h] 000000004A16254A 48 83 C4 20 add rsp,20h 000000004A16254E 41 5D pop r13 000000004A162550 C3 ret ; } end main_parser_fn ; { start get_next_char 000000004A162060 FF F3 push rbx 000000004A162062 48 83 EC 20 sub rsp,20h 000000004A162066 48 8B 05 0B C2 02 00 mov rax,qword ptr [4A18E278h] 000000004A16206D 8B 0D ED 9B 03 00 mov ecx,dword ptr [4A19BC60h] 000000004A162073 33 DB xor ebx,ebx 000000004A162075 66 39 18 cmp word ptr [rax],bx 000000004A162078 74 29 je 000000004A1620A3 ; when bx=0 000000004A16207A 66 83 38 0D cmp word ptr [rax],0Dh ; 0d = \r 000000004A16207E 0F 84 69 0E 00 00 je 000000004A162EED 000000004A162084 3B CB cmp ecx,ebx 000000004A162086 0F 85 46 7A 01 00 jne 000000004A179AD2 000000004A16208C 0F B7 08 movzx ecx,word ptr [rax] 000000004A16208F 48 83 C0 02 add rax,2 000000004A162093 48 89 05 DE C1 02 00 mov qword ptr [4A18E278h],rax 000000004A16209A 66 8B C1 mov ax,cx 000000004A16209D 48 83 C4 20 add rsp,20h 000000004A1620A1 5B pop rbx 000000004A1620A2 C3 ret ; } end get_next_char 000000004A1620A3 E8 18 00 00 00 call 000000004A1620C0 000000004A1620A8 48 8B 05 C9 C1 02 00 mov rax,qword ptr [4A18E278h] 000000004A1620AF 8B 0D AB 9B 03 00 mov ecx,dword ptr [4A19BC60h] 000000004A1620B5 EB C3 jmp 000000004A16207A 000000004A1620C0 ; this starts a large chunk of code that does more parsing (as well as calls ; some CriticalSection code) it's omitted from this because the issues that are prevalent in the ; rest of the code are pertaining to the 'caret_parser' not returning properly. The 'memory leak' ; is in this section code (an 8k buffer read that's also checked in main loop). ; { start quote_parse 000000004A162463 F6 03 22 test byte ptr [rbx],22h 000000004A162466 0F 85 9C 00 00 00 jne 000000004A162508 000000004A16246C B8 00 01 00 00 mov eax,100h 000000004A162471 E9 C0 00 00 00 jmp 000000004A162536 ; } end quote_parse ; { start caret_parse 000000004A170197 F6 03 22 test byte ptr [rbx],22h ; check if '"' 000000004A17019A 0F 85 71 23 FF FF jne 000000004A162511 ; if char == '"' 000000004A1701A0 E8 BB 1E FF FF call 000000004A162060 ; get_next_char 000000004A1701A5 66 89 07 mov word ptr [rdi],ax ; ax will be 0 if EOF 000000004A1701A8 66 41 3B C4 cmp ax,r12w ; r12w is 0x0A ('\n') here, so this is a EOL check (fail in EOF case) 000000004A1701AC 0F 85 82 23 FF FF jne 000000004A162534 ; this is the jump back to the 'main_parser_fn' <--error 000000004A1701B2 E9 0B 99 00 00 jmp 000000004A179AC2 ; == call 000000004A162060 (get_next_char) 000000004A1701B7 33 C9 xor ecx,ecx 000000004A1701B9 E8 22 1B FF FF call 000000004A161CE0 ; } end caret_parse ; end cmd.exe asm code flow 

基于这个程序集,我能够确定cmd.exeparsing代码是这样做的(伪代码):

 void main_parser_fn() { /* the 'some_read_condition' is based on a lot of things but interestingly one of them is an 8k buffer size; the ASM shows an 8191 byte buffer for reading/parsing, but I couldn't ascertain why having a buffer divisible by exactly 8192 bytes in the line buffer was 'ok' but anything more or less causes the continuation (mem leak)?? */ while (some_read_condition) { // allocate 8k buffer appropriately x = get_next_char(); if (x == '|' || x == '&') { main_parser_fn(); } if (x == '^') { get_next_char(); // error here // POSSIBLE FIX: // if (get_next_char() == 0) { abort_batch(); } continue; } // free buffer (never get here due to EOF error) } } 

这当然是基于input/输出和汇编的一个粗略的近似,但是在插入符号后得到下一个字符的代码似乎是问题所在。

错误解释

错误是当检测到插入符号时,从文件中读取下一个字符(被“转义”)。 如果脱字号是文件的最后一个字符,则会导致逻辑错误,因为当调用get_next_char ,文件指针会增加1。 在这种情况下,它通过了EOF 。 由于当命令parsing器继续读取下一个input时, EOF被有效地忽略,由于EOF+1错误,它实际上会“重置”它的文件指针。 在这种情况下,将文件指针放在EOF+1会导致指针处于一个很大的负数,并且由于文件不能低于0,所以文件指针基本上重置为0,parsing从头开始文件。

这解释了内存泄漏,为什么它是8K(一个8K的“读取缓冲区”被填充),也可以解释recursion问题。 当| 或者在batch file中被检测到,它被recursion地parsing出来,并且由于存在一个EOF错误,recursion就变成无限的,因为没有返回path。

编辑 :正如一些评论指出,进一步的研究已经表明,插入不必在文件的末尾发生这个错误,我目前正在调查(并打破更多的ASM),以看看是否有其他情况,以及为什么/如何发生。

修正

看起来,一个简单的解决办法就是在parsing插入符时读取下一个字符时检查EOF 。 这个检查(和随后的“正确的批次中止”function)将解决内存泄漏问题以及无限recursion。

漏洞?

在仔细观察过程并考虑可能的漏洞之后,我并没有看到MS14-019这样严重的问题 ,但考虑到它的易用性和实施​​(以及相对容易的修复),我认为这个“中等因为大多数“漏洞”都需要用户运行batch file,而像试图利用堆栈/帧溢出错误或通过batch file启动shell代码等“明显的”途径,将会比其他漏洞攻击更加困难这将会产生一个比这个无害的批处理bug更有效果的结果。 虽然我可以看到它在DoS攻击中被使用,因为它很容易写7个字节( ^ nul<^ ),并且可能被分发和“设置”相当容易。

这里有一个简单的vbscript,可以用来编写和启动'杀手'batch file(并且默默地执行)

 CreateObject("Scripting.FileSystemObject").CreateTextFile("killer.bat", True).Write("^ nul<^") & VbCr CreateObject("WScript.Shell").Run "killer.bat", 0, False 

这将创build一个名为killer.bat的batch file, killer.bat包含一个^ nul<^\r然后运行它,这可以放在一个.vbs文件中并在启动时运行,或者放入Excelmacros并运行。

 echo|set /p="^ nul<^" > killer.bat 

该行是相当于创build“杀手”batch file的命令行(对文件执行正常的回显会导致文件末尾有\r\n ,从而导致错误不会出现)。

作为概念certificate,我创build了这个vbscript(以及其他一些batch filetesting),并将它们放在我的Startup文件夹和registry中。 当我login时,在使用batch file时遇到了命令提示符,而在使用vbscript时却没有任何问题,几秒钟后,系统停止运行,因为脚本耗尽了所有的RAM,所以无法使用。 脚本可以通过杀死正在运行的cmd.exe进程来结束,但是因为它们工作得太快了,所以在耗用所有RAM之前可能没有足够的时间来启动任务pipe理器。 通过Safe Mode清除是“治愈”,但不是“修复”。

我也可以设想一个情况,一个毫无防备的pipe理员将运行一个backup.bat脚本,并在这个不幸的错误,并无意中closures他们的服务器。 或者在不安全的系统上使用at / schtasks.exe命令可能会带来的乐趣。

当然,我不认为这些“ 漏洞 ”会离开DoS或恶作剧的境界。

我仍在用pipe道和redirect的方式来检查各种途径,可能导致RCE的“破坏”脚本,因为它是最简单的攻击媒介,是一个带有“快速”文件的DoS攻击。 我已经在Windows 98,2000,XP,Vista,7,8以及服务器版本(包括32位和64位版本)上testing了这个错误。 Windows 98命令提示符不受此影响,但其上面的每个版本(包括command.com因为它使用cmd.exe来分析batch file)。 出于好奇,我也在ReactOS和Wine上testing了这个(这两者都没有问题)。

问题 (更多研究后编辑)

如上所述,我并不认为这个bug是被“利用”的,而不是被拒绝服务“攻击”(或者是对同事/朋友的恶作剧),但是让我总体思考了帧溢出和内存泄漏,更具体的如果他们是可以利用的(只是一般)。

从软件工程/黑客的angular度来看,我的经验和理解告诉我,内存泄漏或帧溢出可能会在没有一定保护的较旧的操作系统(比如说Windows 98/2000 / XP或更早版本的* nix?)上潜在地被利用(就像使用NX位或ASLR一样),但是除了“正常”的攻击媒介(基于堆栈的缓冲区溢出)或关于什么的一般文档之外,我还没有find对这些领域的研究这些东西是“是”(即“白皮书”讨论什么是帧溢出或内存泄漏,以及NX / ASLR是什么),而不是“为什么”你不能。

我一直在尝试注入一个线程或其他方法到运行cmd.exe进程运行testing和分析帧溢出和内存泄漏(在这个问题,以及只是一般的乐趣,可以有这个bug ,就像使用CreateProcess然后EmptyWorkingSet一般的乐趣),我知道我不会“得到任何地方”这个特定的错误,但它让我思考(或过度思考):是否曾经有过一个帧溢出漏洞或内存泄漏在野外开发还是有一些我可能能够阅读的文档解释了为什么(更具体地说/技术上)这是不可行的?

我理解“为什么”,但更具体一些,比如“EIP寄存器是受保护的,因为XYZ …”,而不是“不可能”,这会有所帮助; 我知道每个架构都不一样,我可能会要求更多的细节,而不是在答案中,但是我可以参考的链接或讨论点也有帮助,因为我似乎无法find太多的参考。

我一直在大会游泳,一个新的视angular总是帮助:)

注意:我发送了一封电子邮件(在2014年4月25日)给微软这个bug,他们已经回复说他们已经把这个发送给了开发小组并且正在调查,没有计划在安全公告中修正(同意他们因为还没有表明这是一个严重的缺陷)。 编辑应该有更多的更新来。