├── .gitattributes ├── README.md └── mfb.php /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-Mini-File-Browser 2 | 3 | [My writeup about this tool](https://smitka.me/2024/04/12/php-mini-file-browser-update/) 4 | 5 | This is really simple&primitive and dangerous script which allows you: 6 | - iterate throw directory structure and show permissions, it uses 2 methods: 7 | - plain PHP which can be limited via open_basedir 8 | - shell_exec system function which can be limited by disabled_functions 9 | - show basic info about PHP configuraion (version, extensions, disable functions, open_basedir, or complete phpinfo) 10 | - download files from the server (if enabled) 11 | - upload files from URL to the server (if enabled) 12 | - read files and show their content (text, images, archives content) 13 | - run system commands via various methods (if enabled) 14 | 15 | The script will **delete itself after 1 hour** for security reasons (you can configure this behavior). It is also possible to set credentials to use this script, of course. 16 | 17 | > [!CAUTION] 18 | > Do not grant “MFB” access to untrusted users, as a skilled user could escalate their privileges and do anything to your site and server 😉. The script is full of security threats and can cause FPD, XSS, SQLi, SSRF, LFI, RCE, WTF, etc. 19 | 20 | ## File browser 21 | ![mfb-file-browser](https://github.com/lynt-smitka/PHP-Mini-File-Browser/assets/3875093/29802d82-7509-463e-b874-5cefd32350d6) 22 | 23 | ## Dark Mode 😎 24 | ![mfb-file-browser-dark](https://github.com/lynt-smitka/PHP-Mini-File-Browser/assets/3875093/57d9e92f-bb36-4414-a33d-54f145c4977c) 25 | 26 | 27 | ## Command executor 28 | ![mfb-command-executor](https://github.com/lynt-smitka/PHP-Mini-File-Browser/assets/3875093/50f782ba-ec29-4099-86cc-b02ab803a097) 29 | 30 | ## File uploader 31 | ![mfb-file-uploader](https://github.com/lynt-smitka/PHP-Mini-File-Browser/assets/3875093/36438b8e-0609-4ba8-8d09-a42c2cb8a82f) 32 | 33 | ## File reader 34 | 35 | View text files content 36 | 37 | ![mfb-file-reader-text](https://github.com/lynt-smitka/PHP-Mini-File-Browser/assets/3875093/95e5f783-3596-44c0-bb72-67002ad5619b) 38 | 39 | Show images 40 | 41 | ![mfb-file-reader-image](https://github.com/lynt-smitka/PHP-Mini-File-Browser/assets/3875093/63455604-b4d5-4d0e-a996-0e56524db465) 42 | 43 | Show files inside archive (zip, tar, tgz) 44 | 45 | ![mfb-file-reader-archive](https://github.com/lynt-smitka/PHP-Mini-File-Browser/assets/3875093/82503901-ea23-45de-9fab-dad2920ee0cb) 46 | 47 | 48 | 49 | *Note: this project is still alive :-)* 50 | -------------------------------------------------------------------------------- /mfb.php: -------------------------------------------------------------------------------- 1 | $aging) || isset($_GET['remove'])) { 65 | if (unlink(__FILE__)) 66 | die('removed!'); 67 | else 68 | die('not removed!'); 69 | } 70 | 71 | date_default_timezone_set($timezone); 72 | 73 | $method = isset($_GET['m']) ? $_GET['m'] : 'php'; 74 | 75 | if ($download && isset($_GET['down'])) { 76 | download($_GET['down'], $method); 77 | } 78 | 79 | main(__DIR__, $download, $upload, $read, $console, $method, $base64_paths); 80 | 81 | function main($dir, $download, $upload, $read, $console, $method, $base64_paths) 82 | { 83 | echo ""; 84 | echo ""; 85 | echo "

Mini File Browser

"; 86 | 87 | if ($console) { 88 | echo "

Console

"; 89 | console(); 90 | } 91 | 92 | if ($upload) { 93 | echo "

File uploader

"; 94 | upload(); 95 | } 96 | 97 | 98 | if ($read && isset($_GET['read'])) { 99 | echo "

File reader

"; 100 | read($_GET['read'], $method); 101 | } 102 | 103 | 104 | $dir = isset($_GET['dir']) ? ($base64_paths?base64_decode($_GET['dir']):$_GET['dir']) : $dir; 105 | 106 | echo "

File browser

"; 107 | 108 | switch ($method) { 109 | case 'shell_exec': 110 | $files = list_directory_shell($dir); 111 | echo "

File browsing: switch to php

"; 112 | break; 113 | default: 114 | $files = list_directory_php($dir); 115 | $r = is_enabled('shell_exec'); 116 | if ($r[1] === 'b') 117 | echo "

File browsing: switch to shell_exec

"; 118 | break; 119 | } 120 | 121 | echo ""; 122 | 123 | foreach ($files as $file) { 124 | 125 | $file_path = $base64_paths?base64_encode($file["path"]):$file["path"]; 126 | 127 | echo ""; 128 | echo ""; 141 | echo ""; 142 | echo ""; 143 | echo ""; 144 | echo ""; 151 | } 152 | echo "
"; 129 | if ($file['isDir']) { 130 | if ($file['name'] === '..') 131 | echo "[ UP ]"; 132 | elseif ($file['name'] === '.') 133 | echo $file['path']; 134 | else 135 | echo "{$file["name"]}"; 136 | } else { 137 | echo $file["name"]; 138 | } 139 | 140 | echo "{$file['perm']} {$file['owner']}{$file['my_perm']}{$file['time']}"; 145 | if ($download && $file['base64']) 146 | echo "download "; 147 | if ($read && $file['base64']) 148 | echo "read "; 149 | echo $file['size']; 150 | echo "
"; 153 | 154 | echo "

Information

"; 155 | 156 | echo '

Current script: '. __FILE__ . '

'; 157 | echo '

PHP version: ' . f('phpversion') . ' @ ' . f('php_uname') . ' [' . f('php_sapi_name') . ']

'; 158 | echo '

PHP extensions: ' . implode(', ', f('get_loaded_extensions')) . '

'; 159 | echo '

PHP disable functions: ' . ini_get('disable_functions') . '

'; 160 | echo '

PHP dangerous functions: '; 161 | echo is_enabled('system') . ' '; 162 | echo is_enabled('exec') . ' '; 163 | echo is_enabled('shell_exec') . ' '; 164 | echo is_enabled('passthru') . ' '; 165 | echo is_enabled('proc_open') . ' '; 166 | echo is_enabled('popen') . ' '; 167 | echo is_enabled('pcntl_exec') . ' '; 168 | echo is_enabled('putenv') . ' '; 169 | echo '

'; 170 | echo '

Open Basedir: '; 171 | 172 | $basedirs = explode(":", ini_get('open_basedir')); 173 | $num = sizeof($basedirs); 174 | for ($i = 0; $i < $num; $i++) { 175 | echo '' . $basedirs[$i] . ' '; 176 | } 177 | echo '

'; 178 | if ($read) 179 | echo '

Try to read passwd

'; 180 | echo '

PHPinfo()

'; 181 | 182 | 183 | if (isset($_GET['info'])) { 184 | f('phpinfo'); 185 | } 186 | 187 | echo "

Remove me

"; 188 | echo "

Author: Vladimir Smitka, Lynt services s.r.o., Security Blog, GitHub

"; 189 | } 190 | 191 | 192 | 193 | function list_directory_php($dir) 194 | { 195 | $files = scandir($dir); 196 | $fileList = array(); 197 | foreach ($files as $file) { 198 | 199 | $real = @realpath($dir . DIRECTORY_SEPARATOR . $file); 200 | if ($real === false) 201 | continue; 202 | 203 | 204 | $isDir = is_dir($real); 205 | 206 | $perm = get_perms($real); 207 | 208 | $my_perm = ''; 209 | if (f('is_readable',$real)) 210 | $my_perm .= 'R'; 211 | if (f('is_writable',$real)) 212 | $my_perm .= 'W'; 213 | if (f('is_executable',$real)) 214 | $my_perm .= 'X'; 215 | 216 | $owner = ''; 217 | if (function_exists('posix_getpwuid') && function_exists('posix_getgrgid')) { 218 | $pwuid = posix_getpwuid(fileowner($real)); 219 | $grgid = posix_getgrgid(fileowner($real)); 220 | $owner = $pwuid['name'] . ":" . $grgid['name']; 221 | } 222 | 223 | $time = date('Y-m-d H:i:s', filemtime($real)); 224 | $size = is_dir($real) ? '' : FileSizeConvert(filesize($real)); 225 | $base64 = f('is_readable',$real) && !$isDir ? base64_encode($real) : ''; 226 | 227 | $fileList[] = array( 228 | 'name' => $file, 229 | 'path' => $real, 230 | 'isDir' => $isDir, 231 | 'perm' => $perm, 232 | 'my_perm' => $my_perm, 233 | 'owner' => $owner, 234 | 'time' => $time, 235 | 'size' => $size, 236 | 'base64' => $base64 237 | ); 238 | } 239 | return $fileList; 240 | } 241 | 242 | function list_directory_shell($dir) 243 | { 244 | $output = shell_exec("ls -la --time-style=full-iso " . escapeshellarg($dir)); 245 | $lines = explode("\n", trim($output)); 246 | $fileList = array(); 247 | foreach ($lines as $line) { 248 | $line = trim($line); 249 | if ($line === '' || strpos($line, 'total ') === 0) 250 | continue; 251 | $parts = preg_split('/\s+/', $line, 9); 252 | if (count($parts) < 9) 253 | continue; 254 | $file = $parts[8]; 255 | $real = emul_realpath($dir . DIRECTORY_SEPARATOR . $file); 256 | $isDir = $parts[0][0] === 'd'; 257 | $perm = substr($parts[0], 1); 258 | $my_perm = ''; 259 | if (strpos($perm, 'r') !== false) 260 | $my_perm .= 'R'; 261 | if (strpos($perm, 'w') !== false) 262 | $my_perm .= 'W'; 263 | if (strpos($perm, 'x') !== false) 264 | $my_perm .= 'X'; 265 | $owner = $parts[2] . ':' . $parts[3]; 266 | list($timePart) = explode(".", $parts[6]); 267 | $time = date('Y-m-d H:i:s', strtotime($parts[5] . ' ' . $timePart)); 268 | $size = $isDir ? '' : FileSizeConvert($parts[4]); 269 | $base64 = strpos($perm, 'r') !== false && !$isDir ? base64_encode($real) : ''; 270 | 271 | $fileList[] = array( 272 | 'name' => $file, 273 | 'path' => $real, 274 | 'isDir' => $isDir, 275 | 'perm' => $perm, 276 | 'my_perm' => $my_perm, 277 | 'owner' => $owner, 278 | 'time' => $time, 279 | 'size' => $size, 280 | 'base64' => $base64 281 | ); 282 | } 283 | return $fileList; 284 | } 285 | 286 | 287 | function get_perms($file) 288 | { 289 | 290 | $perms = fileperms($file); 291 | 292 | switch ($perms & 0xF000) { 293 | case 0xC000: // socket 294 | $info = 's'; 295 | break; 296 | case 0xA000: // symbolic link 297 | $info = 'l'; 298 | break; 299 | case 0x8000: // regular 300 | $info = '-'; 301 | break; 302 | case 0x6000: // block special 303 | $info = 'b'; 304 | break; 305 | case 0x4000: // directory 306 | $info = 'd'; 307 | break; 308 | case 0x2000: // character special 309 | $info = 'c'; 310 | break; 311 | case 0x1000: // FIFO pipe 312 | $info = 'p'; 313 | break; 314 | default: // unknown 315 | $info = 'u'; 316 | } 317 | // Owner 318 | $info .= (($perms & 0x0100) ? 'r' : '-'); 319 | $info .= (($perms & 0x0080) ? 'w' : '-'); 320 | $info .= (($perms & 0x0040) ? 321 | (($perms & 0x0800) ? 's' : 'x') : 322 | (($perms & 0x0800) ? 'S' : '-')); 323 | // Group 324 | $info .= (($perms & 0x0020) ? 'r' : '-'); 325 | $info .= (($perms & 0x0010) ? 'w' : '-'); 326 | $info .= (($perms & 0x0008) ? 327 | (($perms & 0x0400) ? 's' : 'x') : 328 | (($perms & 0x0400) ? 'S' : '-')); 329 | // World 330 | $info .= (($perms & 0x0004) ? 'r' : '-'); 331 | $info .= (($perms & 0x0002) ? 'w' : '-'); 332 | $info .= (($perms & 0x0001) ? 333 | (($perms & 0x0200) ? 't' : 'x') : 334 | (($perms & 0x0200) ? 'T' : '-')); 335 | 336 | return $info; 337 | 338 | 339 | } 340 | 341 | function upload() 342 | { 343 | 344 | $defaultFilePath = __DIR__ . '/mfb-file.php'; 345 | 346 | echo '
'; 347 | echo '
'; 348 | echo '
'; 349 | echo ''; 350 | echo '
'; 351 | 352 | if (isset($_POST['fileUpload'])) { 353 | 354 | $fileUrl = $_POST['fileUrl']; 355 | $filePath = $_POST['filePath']; 356 | $fileContent = file_get_contents($fileUrl); 357 | if ($fileContent !== false) { 358 | file_put_contents($filePath, $fileContent); 359 | } 360 | } 361 | 362 | 363 | } 364 | function console() 365 | { 366 | 367 | $method = isset($_POST['method']) ? $_POST['method'] : 'system'; 368 | 369 | echo '
'; 370 | echo ' system()
'; 371 | echo ' backtick
'; 372 | echo ' exec()
'; 373 | echo ' shell_exec()
'; 374 | echo ' passthru()
'; 375 | echo ' proc_open()
'; 376 | echo ' popen()
'; 377 | echo ' pcntl_exec() (/bin/sh -c cmd > outfile)
'; 378 | echo ' eval()
'; 379 | echo ''; 380 | echo ''; 381 | echo '
'; 382 | 383 | if (isset($_POST['command'])) { 384 | $command = escapeshellcmd($_POST['command']); 385 | echo '

Result:

';
386 |     switch ($_POST['method']) {
387 | 
388 |       case 'system':
389 |         echo system($command);
390 |         break;
391 | 
392 |       case 'backtick':
393 |         echo `$command`;
394 |         break;
395 | 
396 |       case 'exec':
397 |         exec($command, $tmp);
398 |         print_r($tmp);
399 |         break;
400 | 
401 |       case 'shell_exec':
402 |         echo shell_exec($command);
403 |         break;
404 | 
405 |       case 'passthru':
406 |         passthru($command);
407 |         break;
408 | 
409 |       case 'eval':
410 |         echo eval_code($_POST['command']);
411 |         break;
412 | 
413 |       case 'proc_open':
414 |         $pr = proc_open($command, array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes);
415 |         echo stream_get_contents($pipes[1]);
416 |         fclose($pipes[0]);
417 |         fclose($pipes[1]);
418 |         fclose($pipes[2]);
419 |         break;
420 | 
421 |       case 'popen':
422 |         $fp = popen($command, "r");
423 |         echo stream_get_contents($fp);
424 |         fclose($fp);
425 |         break;
426 | 
427 |       case 'pcntl_exec':
428 |         header("Refresh:1");
429 |         pcntl_exec('/bin/sh', array('-c', $command . ' > this_is_pcntl_exec_outfile.txt'));
430 |         break;
431 |     }
432 | 
433 |     echo '

'; 434 | } 435 | if (file_exists('this_is_pcntl_exec_outfile.txt')) { 436 | echo '

Result:

';
437 |     echo file_get_contents('this_is_pcntl_exec_outfile.txt');
438 |     echo "
"; 439 | unlink('this_is_pcntl_exec_outfile.txt'); 440 | } 441 | 442 | } 443 | 444 | 445 | function eval_code($code) 446 | { 447 | if (!preg_match('/\breturn\b/', $code)) { 448 | $code = 'return ' . $code; 449 | } 450 | if (substr(trim($code), -1) !== ';') { 451 | $code .= ';'; 452 | } 453 | try { 454 | $result = eval ($code); 455 | } catch (ParseError $e) { 456 | return 'Parse error: ' . $e->getMessage(); 457 | } 458 | return $result; 459 | } 460 | 461 | function emul_realpath($path) 462 | { 463 | $folders = explode('/', $path); 464 | $stack = array(); 465 | foreach ($folders as $folder) { 466 | if ($folder === '..') { 467 | array_pop($stack); 468 | } elseif ($folder !== '' && $folder !== '.') { 469 | array_push($stack, $folder); 470 | } 471 | } 472 | $result = '/' . implode('/', $stack); 473 | if (substr($path, -1) === '/') { 474 | $result .= '/'; 475 | } 476 | return $result; 477 | } 478 | 479 | function modify_url($key, $value) 480 | { 481 | $parts = parse_url($_SERVER['REQUEST_URI']); 482 | parse_str(isset($parts['query']) ? $parts['query'] : '', $query); 483 | $query[$key] = $value; 484 | $parts['query'] = http_build_query($query); 485 | return $parts['path'] . '?' . $parts['query']; 486 | } 487 | 488 | function FileSizeConvert($bytes) 489 | { 490 | $units = array("TB" => pow(1024, 4), "GB" => pow(1024, 3), "MB" => pow(1024, 2), "kB" => 1024, "B" => 1); 491 | foreach ($units as $unit => $value) { 492 | if ($bytes >= $value) { 493 | return str_replace(".", ",", round($bytes / $value, 2)) . " " . $unit; 494 | } 495 | } 496 | return '0 B'; 497 | } 498 | 499 | 500 | function is_enabled($func) 501 | { 502 | if (!function_exists($func)) { 503 | return "$func"; 504 | } 505 | 506 | $disabledFunctions = array_map('trim', explode(',', ini_get('disable_functions'))); 507 | 508 | if (in_array($func, $disabledFunctions)) { 509 | return "$func"; 510 | } 511 | 512 | return "$func"; 513 | 514 | } 515 | 516 | function download($file, $method) 517 | { 518 | $file = base64_decode($file); 519 | header("Content-Type: application/octet-stream"); 520 | header("Content-Transfer-Encoding: Binary"); 521 | header("Content-disposition: attachment; filename=\"" . basename($file) . "\""); 522 | switch ($method) { 523 | case "php": 524 | readfile($file); 525 | break; 526 | case "shell_exec": 527 | echo shell_exec("cat " . escapeshellarg($file) . " 2>&1"); 528 | break; 529 | } 530 | 531 | exit(); 532 | } 533 | 534 | function read($file, $method) 535 | { 536 | switch ($file) { 537 | case 'predefined1': 538 | $file = "/etc/passwd"; 539 | break; 540 | default: 541 | $file = base64_decode($file); 542 | } 543 | 544 | echo "

File: $file

"; 545 | 546 | $ext = pathinfo($file, PATHINFO_EXTENSION); 547 | $file_name = pathinfo($file, PATHINFO_BASENAME); 548 | echo "
";
549 |   ob_start();
550 | 
551 |   switch ($method) {
552 |     case "php":
553 |       readfile($file);
554 |       break;
555 |     case "shell_exec":
556 |       echo shell_exec("cat " . escapeshellarg($file) . " 2>&1");
557 |       break;
558 |   }
559 | 
560 |   $content = ob_get_clean();
561 |   $is_img = false;
562 |   $is_archive = false;
563 |   $mime = 'text/plain';
564 | 
565 |   switch ($ext) {
566 |     case "jpg":
567 |       $is_img = true;
568 |       $mime = 'image/jpeg';
569 |       break;
570 |     case "jpeg":
571 |       $is_img = true;
572 |       $mime = 'image/jpeg';
573 |       break;
574 |     case "png":
575 |       $is_img = true;
576 |       $mime = 'image/png';
577 |       break;
578 |     case "gif":
579 |       $is_img = true;
580 |       $mime = 'image/gif';
581 |       break;
582 |     case "webp":
583 |       $is_img = true;
584 |       $mime = 'image/webp';
585 |       break;
586 |     case "svg":
587 |       $is_img = true;
588 |       $mime = 'image/svg+xml';
589 |       break;
590 |     case "zip":
591 |       $is_archive = true;
592 |       break;
593 |     case "tgz":
594 |       $is_archive = true;
595 |       break;
596 |     case "tar":
597 |       $is_archive = true;
598 |       break;
599 |     case "gz":
600 |       $is_archive = true;
601 |       break;
602 | 
603 |     default:
604 |       $is_img = false;
605 |   }
606 | 
607 |   if ($is_img) {
608 |     echo '';
609 |   } elseif ($is_archive) {
610 |     read_archive($content, $file_name);
611 |   } else {
612 |     echo htmlspecialchars($content);
613 |   }
614 |   echo "
"; 615 | 616 | } 617 | 618 | 619 | function read_archive($content, $file_name) 620 | { 621 | if (class_exists("PharData")) { 622 | $temp = sys_get_temp_dir() . '/mfb-archive-' . $file_name; 623 | file_put_contents($temp, $content); 624 | $phar = new PharData($temp); 625 | echo "Archive files:
"; 626 | foreach (new RecursiveIteratorIterator($phar) as $file) { 627 | echo $file->getFilename() . "
"; 628 | } 629 | unlink($temp); 630 | } 631 | } 632 | 633 | function f($function) { 634 | $params = func_get_args(); 635 | array_shift($params); 636 | 637 | if (function_exists($function)) { 638 | try { 639 | return call_user_func_array($function, $params); 640 | } catch (Throwable $e) { 641 | return null; 642 | } 643 | } 644 | return null; 645 | } --------------------------------------------------------------------------------