【ViewState 指北】校验和解密数据

admin 2025年6月24日23:35:17评论3 views字数 5348阅读17分49秒阅读模式

0x00 前言

本文需要读者对 ViewState 有一定的了解。

ASP.NET 的 ViewState 数据的序列化和反序列化均由 ObjectStateFormatter完成。对于签名/加密,是由多个参数因素决定了生成的数据,其中 CompatibilityMode的值是分支的节点:

  1. 1. 当 CompatibilityMode为 Framework20SP1时,可以由 GetEncodeData或 EncryptOrDecryptData函数生成最终的数据。
  2. 2. 当 CompatibilityMode为 Framework45时,只能由 Purpose生成数据。

本文主要是针对 DOTNET20和 DOTNET45两种类型的 ViewState 数据的一个结构进行解析,通俗的解释爆破的原理。

下图是作者当时在学习时编写的导图。

【ViewState 指北】校验和解密数据

PS:由于 Protect 的结果是直接调用代码的输出格式,但实际上把 ||换成 + 就好理解了。

核心思路:由于 ViewState 都是数据+数据签名,因此最直接的爆破思路就是根据签名的长度,拆分 ViewState,然后再使用对应的签名算法进行签名,对比前后两个签名是否一致,一致则继续往下决定是否解密。

0x01 当 CompatibilityMode为 Framework20SP1

当 CompatibilityMode为 Framework20SP1时,存在三种情况:

  1. 1. 仅进入 GetEncodeData 函数,那生成的数据就是不加密的 ViewState(仅序列化)+ 签名。
  2. 2. 当签名算法是 3DES/AES 时,那么先进入 GetEncodeData 函数,再进入EncryptOrDecryptData 函数。
  3. 3. 仅进入 EncryptOrDecryptData 函数。

1.1 GetEncodeData 函数

当签名算法不是 3DES/AES 时(也就是非加密时),那么我们的 ViewState 数据就是 buf+hmac(buf+modifier),那么从爆破的角度,它是有更优解的,可以直接解析 ViewState,从而获取到签名长度,就得出对应签名算法。

由于 Buf 是经过 ObjectStateFormatter序列化的,因此看看反序列化的代码(挑重点看):

publicvoidSerialize(Stream outputStream, object stateGraph)
{
if (outputStream == null)
thrownew ArgumentNullException(nameof (outputStream));
this.InitializeSerializer();
    ObjectStateFormatter.SerializerBinaryWriter writer = new ObjectStateFormatter.SerializerBinaryWriter(outputStream);
    writer.Write(byte.MaxValue);  // 数据开头直接写入 255
    writer.Write((byte1);    // 第二个则是 1
this.SerializeValue(writer, stateGraph);
}


privatevoidSerializeValue(ObjectStateFormatter.SerializerBinaryWriter writer, objectvalue)
{
// 创建一个 Stack 对象,用于存储需要序列化的对象。 这使得该方法能够处理嵌套的对象结构。
// 通过循环进行值类型处理
// 也就是说这里通过 white 对控件等数据全部进行序列化
}

publicobjectDeserialize(Stream inputStream)
{
if (inputStream == null)
    {
thrownew ArgumentNullException("inputStream");
    }

    Exception innerException = null;
    InitializeDeserializer();
    SerializerBinaryReader serializerBinaryReader = new SerializerBinaryReader(inputStream);
try
    {
byte b = serializerBinaryReader.ReadByte();
if (b == byte.MaxValue)  // 查看第一个 byte 是否为 255
        {
byte b2 = serializerBinaryReader.ReadByte();
if (b2 == 1// 查看第二个 byte 是否为 1
            {
return DeserializeValue(serializerBinaryReader);
            }
        }
    }
catch (Exception ex)
    {
        innerException = ex;
    }

thrownew ArgumentException(SR.GetString("InvalidSerializedData"), innerException);
}


privateobjectDeserializeValue(SerializerBinaryReader reader)
{
byte b = reader.ReadByte(); // 第三个 byte 表示该进入什么分支对数据进行处理,自己编写生成的 ViewState 固定为 50,但是实际环境是多层嵌套,不是固定的
switch (b)
    {
case100:
returnnull;
case101:
returnstring.Empty;
case5:
return reader.ReadString();
case102:
return0;
        ....
case50:
            {
int num = reader.ReadEncodedInt32(); // 第四个 byte 是使用 7-bit encoded integer 格式来表示的长度
byte[] buffer = newbyte[num];
if (num != 0)
                {
                    reader.Read(buffer, 0, num);
                }

object result = null;
                MemoryStream memoryStream = GetMemoryStream();
try
                {
                    memoryStream.Write(buffer, 0, num);
                    memoryStream.Position = 0L;
                    IFormatter formatter = new BinaryFormatter();
                    result = formatter.Deserialize(memoryStream);
                }
catch (Exception exception)
                {
if (_throwOnErrorDeserializing)
                    {
throw;
                    }

                    WebBaseEvent.RaiseSystemEvent(SR.GetString("Webevent_msg_OSF_Deserialization_Binary"), this30110, exception);
                }
finally
                {
                    ReleaseMemoryStream(memoryStream);
                }

return result;
            }
default:
thrownew InvalidOperationException(SR.GetString("InvalidSerializedData"));
    }
}

在实际环境当中,由于 ViewState 是多层嵌套的数据结构,是根据页面上所有控件的状态序列化而成,因此在反序列化时是需要逐个解析。但实际过程中,我们不需要去理解这个过程,我们只需要记住:

对于未加密的 ViewState,数据开头的 Hex 均为 0xff,0x01,也就是对应代码当中的 255,1,接下来则是所嵌套的控件依次反序列化解析内容。

当解析控件的反序列化内容后,剩下的就是签名。(如果没有剩余的签名,那么这个网站就是可以任意进行 ViewState 反序列化攻击)

在得到剩下的签名数据后,可以根据签名的长度,可以得出是什么签名算法,因此可以排除掉其他的算法,大大提升了爆破的速度。

下面算法对应的签名长度:

算法
长度
MD5
16
SHA1(AES/3DES)
20
SHA256
32
SHA384
48
SHA512
64

编写解析 ViewState 的代码可以直接对标官方代码中的 DeserializeValue,也可以参考:BurpSuite 的 ViewState 插件:https://github.com/raise-isayan/ViewStateDecoder/blob/master/src/main/java/aspx/viewstate/ViewStateParser.java

随便一个 ViewState 解析的结果如下:

ParseResult {
  Value: Pair {
    First: Pair {
      First: "-162691655"
      Second: Pair {
        First: Array[2] {
          [0]: "Counter"
          [1]: 0
        }
        Second: Array[2] {
          [0]: 3
          [1]: Pair {
            First: null
            Second: Array[2] {
              [0]: 1
              [1]: Pair {
                First: Pair {
                  First: Array[2] {
                    [0]: "Text"
                    [1]: "Counter: 0"
                  }
                  Second: null
                }
                Second: null
              }
            }
          }
        }
      }
    }
    Second: null
  }
  Remaining: Array[32] {
    [0]: 84
    [1]: 191
    [2]: 239
    [3]: 49
    [4]: 29
    [5]: 150
    [6]: 237
    [7]: 229
    [8]: 189
    [9]: 125
    [10]: 35
    [11]: 215
    [12]: 159
    [13]: 252
    [14]: 192
    [15]: 87
    [16]: 16
    [17]: 119
    [18]: 72
    [19]: 86
    [20]: 112
    [21]: 3
    [22]: 7
    [23]: 228
    [24]: 112
    [25]: 215
    [26]: 22
    [27]: 202
    [28]: 155
    [29]: 165
    [30]: 31
    [31]: 248
  }
}

Remaining 就是余下的签名数据。

1.2 EncryptOrDecryptData 函数

其实,进入 EncryptOrDecryptData函数生成的数据,都是经过加密并附带签名的数据,也就是图中的 E(iv+buf+modifier) + HMAC(E(iv+buf+modifier))但是经过加密的数据,没有像上面那样具备标识值,因此对于签名算法只能通过遍历进行枚举。

这里我们并不要求能够解密经过加密的数据,只要有签名密钥就可以生成 ViewState 数据。但当签名算法是 3DES/AES 时,会出现一个特殊的情况,由于签名算法是使用 SHA1,因此旧版本爆破结果会显示为 SHA1。所以这种情况是需要对后续的数据进行解密的,在解密完成之后,还需要对解密后的数据进行解析,查看是否有剩余的 Remaining,如果有剩余且长度为 20,那么输出的签名算法是显示:AES/3DES

当签名算法为 AES/3DES的时候,必须要填写DecryptionKey才能连接成功,并且 ValidationKey 可以填写为 SHA1/AES/3DES,这三种都可以连接成功。

0x02 当 CompatibilityMode为 Framework45

本质上和 EncryptOrDecryptData 是没有区别的,仅多了使用 SP 800-108 的派生算法,并且数据中多了加密所使用的 IV。解密后去除这些内容即可。

0x03 当前工具的爆破结果

由于某些特殊环境,因此新增了一个 Proxy 可供填写。

3.1 常规数据

【ViewState 指北】校验和解密数据

3.2 加密的数据

  • • .NET 20SP1
【ViewState 指北】校验和解密数据
  • • .NET 45
【ViewState 指北】校验和解密数据

3.3 当签名算法是 3DES/AES 时

【ViewState 指北】校验和解密数据

原文始发于微信公众号(RowTeam):【ViewState 指北】校验和解密数据

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月24日23:35:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【ViewState 指北】校验和解密数据https://cn-sec.com/archives/4195691.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息