Race Condition is a method of attack consisting of executing a query in a shorter time than the verification of the conditions of a given application action, e.g., when uploading files to the server, the time between saving the file on the disk and verifying its type or extension allows you to execute a query to the stored resource. Such a process, described in a pseudo language, would look like this:
1 2 3 4 5 6 7 |
UploadFile ( Resource ); IF ( CheckExtension ( Resource ) == ‘JPG’ ) THEN ShowMessage ( ‘Uploaded RESOURCE’ ); IN OTHER CASE DeleteFile ( Resource ); ShowMessage ( ‘Resource has wrong extension’ ); END IF; |
As you can see in the example above, the file is uploaded first and then its extension is verified. The time it takes for the script to verify the extension of the file and, if it is incompatible with deleting it, allows the script to refer to the file until it still exists.
How to carry out this type of attack
Simultaneously upload the file to the server and refer to the uploaded resource. For demonstration purposes we have created a simple code in PHP, allowing for a file upload.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<!DOCTYPE html> <html> <body> <form method="post" enctype="multipart/form-data"> Select image to upload: <input type="file" name="fileToUpload" id="fileToUpload"> <input type="submit" value="Upload Image" name="submit"> </form> <?php $target_dir = "uploads/"; $target_file = $target_dir . basename(@$_FILES["fileToUpload"]["name"]); if(isset($_POST["submit"])) { $finfo = finfo_open(FILEINFO_MIME); $mime = explode(";", finfo_file($finfo, $_FILES["fileToUpload"]["tmp_name"]))[0]; finfo_close($finfo); if($mime == "image/png" || $mime == "image/gif" || $mime == "image/jpg") { move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file); $ext = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); if($ext == "png" || $ext == "gif" || $ext == "jpg") { echo "File uploaded - $target_file"; } else { echo "File extension error - $target_file"; unlink($target_file); } } else { echo "File content type error - $target_file"; } } ?> </body> </html> |
Let’s assume that the above code is saved on the local station as “upload.php”. The code may seem safe, but only at first glance, because despite the verification of the file extension, and even its type – it is simply verified in the wrong order.
In order to launch an attack on the application mentioned above, hundreds of upload queries have to be sent simultaneously.
File upload query:
The PHP code has been deliberately integrated into a fragment of the PNG image in order to bypass “mime” filters.
Query calling action with our shell (version for Linux OS):
1 |
http://localhost/uploads/shellimg.php?command=cp%20shellimg.php%20shell.php |
What next?
Now it is enough to run the appropriate script or iterations with Intruder in BURP/ZAP, which will perform hundreds of attempts to upload the file for us and call an action in our shell, aimed to copy itself to a safe place for further exploitation.
If the script checking the value of the extension does not manage to delete the malicious file and our program invokes copying action, we will get access to a stable shell version at the address:
1 |
http://localhost/uploads/shell.php |
How to protect yourself
It is important to run (necessary) security tests on contents of a file before it is uploaded to a public directory.
Safe version of the code (not including XSS attack 😉 ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<!DOCTYPE html> <html> <body> <form method="post" enctype="multipart/form-data"> Select image to upload: <input type="file" name="fileToUpload" id="fileToUpload"> <input type="submit" value="Upload Image" name="submit"> </form> <?php $target_dir = "uploads/"; $target_file = $target_dir . basename(@$_FILES["fileToUpload"]["name"]); if(isset($_POST["submit"])) { $finfo = finfo_open(FILEINFO_MIME); $mime = explode(";", finfo_file($finfo, $_FILES["fileToUpload"]["tmp_name"]))[0]; finfo_close($finfo); if($mime == "image/png" || $mime == "image/gif" || $mime == "image/jpg") { $ext = strtolower(pathinfo($_FILES["fileToUpload"]["tmp_name"],PATHINFO_EXTENSION)); if($ext == "png" || $ext == "gif" || $ext == "jpg") { move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file); echo "File uploaded - $target_file"; } else { echo "File extension error - $target_file"; } } else { echo "File content type error - $target_file"; } } ?> </body> </html> |