对用户提供的数据进行二进制反序列化是否安全?

信息安全 。网
2021-08-26 10:47:29

AppHarbor 有一篇博客文章,其中包含示例 C# 代码,该代码从未签名的 cookie 中读取数据并将其传递给 .Net 二进制序列化。

那安全吗?

显然,数据是完全可篡改的。

但是,将攻击者控制的输入传递给反序列化器是否有任何风险?
这会导致代码执行吗?

4个回答

我知道这在游戏中真的非常晚,但是的,有一个特定的漏洞直接与不受信任的数据的反序列化有关。它的工作方式与所谓的“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对象,会发生什么?

发生了四件事:

  1. BinaryFormatter 将数据反序列化为对象实例。
  2. 返回的对象未成功转换为 Message 对象,引发转换异常。
  3. 该方法在不使用返回的对象的情况下退出,在 GC 树中留下零引用的内部表示。
  4. 垃圾收集器处理对象。

为什么这是可利用的?那么,当GC调用终结逻辑时,析构函数调用TempFile.Dispose()并删除该_fileName字段指定的文件。由于该字段的值是在二进制序列化 blob 中设置的,因此攻击者可以控制它。把它们放在一起,你就有了一个任意文件删除错误。

这似乎有点牵强,但通常您会在Dispose()可序列化类或析构函数中发现 SQL 查询和各种其他好东西。

编辑:我快速搜索了参考源,结果发现它System.CodeDo m.Compiler.TempFileCollection包含我上面显示的问题 - 你可以用文件列表填充它,并在销毁时将它们全部删除。

我对 C# 的序列化知之甚少,不知道这是否存在安全风险,但我可以告诉你:

在 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都不会执行。