AppHarbor 有一篇博客文章,其中包含示例 C# 代码,该代码从未签名的 cookie 中读取数据并将其传递给 .Net 二进制序列化。
那安全吗?
显然,数据是完全可篡改的。
但是,将攻击者控制的输入传递给反序列化器是否有任何风险?
这会导致代码执行吗?
AppHarbor 有一篇博客文章,其中包含示例 C# 代码,该代码从未签名的 cookie 中读取数据并将其传递给 .Net 二进制序列化。
那安全吗?
显然,数据是完全可篡改的。
但是,将攻击者控制的输入传递给反序列化器是否有任何风险?
这会导致代码执行吗?
我知道这在游戏中真的非常晚,但是是的,有一个特定的漏洞直接与不受信任的数据的反序列化有关。它的工作方式与所谓的“PHP 对象注入”攻击相同 - 通过滥用在其最终确定和处置阶段执行操作的类。
考虑以下类:
[Serializable]
class TempFile : IDisposable
{
private readonly string _fileName;
public TempFile()
{
_fileName = Path.GetTempFileName();
}
~TempFile()
{
Dispose();
}
public FileStream GetStream()
{
return new FileStream(_fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
}
public void Dispose()
{
try
{
if (File.Exists(_fileName))
File.Delete(_fileName);
} catch {}
}
}
现在,想象一下有一个非常无聊的可序列化类Message
,它只包含一些字符串。没有什么真正有趣的。您正在使用此类通过网络传递消息,如下所示:
public void ProcessNetworkMessage(byte[] message)
{
var bf = new BinaryFormatter();
Message msg = null;
try
{
using (var ms = new MemoryStream(message))
{
msg = (Message) bf.Deserialise(ms);
}
}
catch (Exception ex)
{
AppLog.WriteLine("Exception: " + ex.Message);
}
if (msg != null)
{
ProcessMessage(msg);
}
}
看起来无害,不是吗?但是,如果攻击者通过TempFile
网络传递对象的二进制序列化版本,而不是预期的Message
对象,会发生什么?
发生了四件事:
为什么这是可利用的?那么,当GC调用终结逻辑时,析构函数调用TempFile.Dispose()
并删除该_fileName
字段指定的文件。由于该字段的值是在二进制序列化 blob 中设置的,因此攻击者可以控制它。把它们放在一起,你就有了一个任意文件删除错误。
这似乎有点牵强,但通常您会在Dispose()
可序列化类或析构函数中发现 SQL 查询和各种其他好东西。
编辑:我快速搜索了参考源,结果发现它System.CodeDo
m.Compiler.TempFileCollection
包含我上面显示的问题 - 你可以用文件列表填充它,并在销毁时将它们全部删除。
我对 C# 的序列化知之甚少,不知道这是否存在安全风险,但我可以告诉你:
在 Java 中,反序列化不受信任的数据是不安全的。有许多微妙的安全陷阱可以真正让您感到困惑。如果您是大师级别的,您可能可以避免这些陷阱,但普通开发人员可能对这些危险一无所知。
以下是一些微妙点的例子:
如果要反序列化不受信任的数据,则必须编写特殊的反序列化代码来防御,例如破坏代码安全不变性的恶意字节序列。
如果您在构造函数或工厂方法中进行任何安全检查,则必须在特殊的反序列化方法中复制它们。如果您在构造函数或工厂方法中调用 SecurityManager,则必须在特殊的反序列化方法中复制这些调用。
在 Java 标准库类中如何使用反序列化存在一些不为人知的安全漏洞。然后再多一些。还有更多。甚至更多。你还无聊还是想要另一个?
顺便说一下,这个例子说明了反序列化和 Java 权限模型(基于堆栈检查)之间微妙而危险的交互。
有一些与反序列化相关的疯狂的毛茸茸的安全陷阱(1)允许攻击者改变不应该可变的字段,或者(2)允许攻击者利用并发/重入错误来破坏安全关键对象不变量或学习秘密您不应该能够学习的值,或者 (3) 观察未完全初始化的对象(从而允许改变原本不可变的对象)。
以下是 Java 反序列化的一些更疯狂的风险。
如果您完全理解所有这些并能牢记在心,那么您可能是编写可以安全处理不受信任字节流的安全反序列化代码的好人选。另一方面,如果你只是一个凡人(像我一样),对整个事情感到厌恶而举手,那么确保你永远不会反序列化不受信任的数据可能是谨慎的。
在您给出的反序列化 cookie 值的示例中,这是一种看似合理的防御措施,您可以使用它来确保您永远不会反序列化不受信任的字节流。生成 cookie 时,您可以在 cookie 名称和值上计算消息验证码 (MAC),并将其附加到 cookie 值。收到 cookie 时,可以检查 MAC 是否有效。您的服务器需要生成一个随机 MAC 密钥并安全地存储它,但它不需要与其他任何人(除了提供请求的所有服务器)共享这个秘密值。
除了明显的篡改弱点之外,这样做并没有错。只是不要相信来自客户端的数据。
据我所知,.NET 序列化库没有出色的代码执行漏洞。
在进一步检查中,我注意到该对象被反序列化,IDictionary
无法想象攻击者可以创建一个实现但包含任意代码的对象。我对 .NET 的二进制序列化实现知之甚少,不知道这是否会立即成功,但取决于实现它是可行的。Dictionary
<string,object>
IDictionary
序列化是潜在的攻击媒介吗?是的。是在这种情况下吗?Nnnnnyesssss没有。我认为这需要 BinaryFormatter 中目前不存在的漏洞(至少,公开地)。
该漏洞将存在于反序列化后使用 IDictionary 的代码中。
我认为这是写得很糟糕的代码,但并不是那么有害,因为调用代码中会存在漏洞。如果反序列化以外IDictionary<string, object>
的内容,则强制转换不会引发异常。它将简单地返回一个空值。一个演员像
thing2 foo = thing1 as thing2
将返回空值。一个演员像
thing2 foo = (thing2)thing1
将抛出一个强制转换异常。
这object
也必须是可序列化的,因此您仅限于 CLR 中当前可序列化的内容以及程序集/应用程序知道可序列化的内容(这也适用于IDictionary<>
. 然后必须将字典中的那个对象转换为有用的东西,或者需要使用反射来从中获取数据。但是,直到该对象被实际使用之前,Dictionary<>
or中的代码object
都不会执行。