├── .gitignore ├── README.markdown ├── TypeNormalizer.php ├── fuzz.php └── config.php /.gitignore: -------------------------------------------------------------------------------- 1 | cwd/ 2 | results/ 3 | php_manual_en.sqlite 4 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Phuzzy 2 | ====== 3 | 4 | This is a PHP fuzzer. Fuzzing is a technique of finding bugs by generating 5 | random function calls. 6 | 7 | This script needs an sqlite database containing function information. This 8 | database can be obtained from http://doc.php.net/downloads/sqlite/php_manual_en.sqlite. 9 | 10 | (Code is dirty as this is mainly for personal use, sorry.) 11 | -------------------------------------------------------------------------------- /TypeNormalizer.php: -------------------------------------------------------------------------------- 1 | resourceFunctionRegex = '/^(' . implode('|', $resourceFunctionPrefixes) . ')_/'; 9 | } 10 | 11 | public function normalize($type, $function) { 12 | // use a general number type instead of float/int (they are usually 13 | // usable interchangably and we might catch some strange edge case 14 | // bugs through this) 15 | if ($type == 'float' || $type == 'int') { 16 | return 'number'; 17 | } 18 | 19 | // use specific resource types (like ftpResource) instead of generic 20 | // resource type 21 | if ($type == 'resource') { 22 | if (preg_match($this->resourceFunctionRegex, $function, $matches)) { 23 | return $matches[1] . 'Resource'; 24 | } else { 25 | // default resource is fileResource 26 | return 'fileResource'; 27 | } 28 | } 29 | 30 | // leave other types untouched 31 | return $type; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /fuzz.php: -------------------------------------------------------------------------------- 1 | prepare($query); 11 | $stmt->execute(array_slice(func_get_args(), 1)); 12 | return $stmt; 13 | } 14 | } 15 | 16 | $db = new InvokablePDO('sqlite:' . $sqliteFile); 17 | $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 18 | 19 | $typeNormalizer = new TypeNormalizer($resourceFunctionPrefixes); 20 | 21 | while (true) { 22 | while (true) { 23 | // start off with just initial code segment 24 | // substitute {{ }} inline expressions 25 | $code = preg_replace_callback( 26 | '(\{\{(.*?)\}\})', 27 | function ($matches) { 28 | return eval('return ' . $matches[1] . ';'); 29 | }, 30 | $initCode 31 | ) . "\n\n"; 32 | 33 | // and use initial variables 34 | $vars = $initVars; 35 | 36 | for ($i = 1; $i <= $n; ++$i) { 37 | while (true) { 38 | $function = $functions[array_rand($functions)]; 39 | 40 | if (!$functionInfo = $db('SELECT return_type FROM functions WHERE name = ?', $function)->fetch()) { 41 | // unknown function, skip 42 | continue; 43 | } 44 | 45 | $args = array(); 46 | foreach ($db('SELECT type FROM params WHERE function_name = ?', $function) as $param) { 47 | $type = $param['type']; 48 | 49 | if ($type == 'mixed') { 50 | $type = array_rand($vars); 51 | } 52 | 53 | $type = $typeNormalizer->normalize($type, $function); 54 | 55 | if (!isset($vars[$type])) { 56 | // don't know that type right now, choose another function 57 | continue 2; 58 | } 59 | 60 | $applicableVars = $vars[$type]; 61 | $args[] = '$' . $applicableVars[array_rand($applicableVars)]; 62 | } 63 | 64 | $returnType = $typeNormalizer->normalize($functionInfo['return_type'], $function); 65 | $returnVarName = $returnType . '_' . $function . '_' . $i; 66 | 67 | $code .= "\necho \"Running $i/$n ($function).\\n\";" 68 | . "\n$$returnVarName = $function(" . implode(', ', $args) . ");"; 69 | 70 | $vars[$returnType][] = $returnVarName; 71 | 72 | break; 73 | } 74 | } 75 | 76 | // ensure that cwd is still writable 77 | chmod($cwd, 0755); 78 | 79 | file_put_contents($generatedFile, $code); 80 | 81 | $output = array(); 82 | exec("cd $cwd; php -f $generatedFile 1>$stdoutFile 2>$stderrFile", $output, $return); 83 | 84 | $stderr = file_get_contents($stderrFile); 85 | 86 | echo $stderr; 87 | 88 | // Return code 255 means that some error occured, so print it 89 | if ($return == 255) { 90 | $lastLine = `tail -n 1 $stdoutFile`; 91 | 92 | // if "thrown in" occurs (Exception) print a few more lines 93 | if (preg_match('(thrown in)', $lastLine)) { 94 | echo 'Last ten lines of output:', "\n", `tail -n 10 $stdoutFile`; 95 | } else { 96 | echo 'Last line of output:', "\n", $lastLine; 97 | } 98 | } 99 | 100 | echo 'Return: ', $return, "\n"; 101 | 102 | if ($return == 139) { 103 | $type = 'segfault'; 104 | break; 105 | } elseif ($return != 0 && $return != 255) { 106 | $type = 'unsuccessful'; 107 | break; 108 | } elseif (preg_match('(memory leak)', $stderr)) { 109 | $type = 'memory_leak'; 110 | break; 111 | } elseif (preg_match('(inconsistent)', $stderr)) { 112 | $type = 'inconsistent'; 113 | break; 114 | } elseif (preg_match('(zero-terminated)', $stderr)) { 115 | $type = 'zero_terminated'; 116 | break; 117 | } 118 | } 119 | 120 | copy($generatedFile, $results . '/investigate_' . uniqid() . '_' . $type . '.php'); 121 | } 122 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | -5, 100 => 17, 0 => 'a', 'a' => 0, 1 => 'b', 'b' => 1); 65 | 66 | // file resources 67 | $fileResourceTemp = fopen('php://temp', 'wr'); 68 | 69 | // callbacks 70 | $callbackInvalid = "\0\1"; 71 | $callbackStrlen = 'strlen'; 72 | $callbackClosure = function() { return 123; }; 73 | $callbackByRef = function(&$a, &$b, &$c) { return 321; }; 74 | 75 | // bools ... 76 | $boolTrue = true; 77 | $boolFalse = false; 78 | EOC; 79 | 80 | // Initial type variables 81 | $initVars = array( 82 | 'number' => array( 83 | 'intMax', 'intMaxPre', 'intMinPre', 'intMin', 84 | 'intZero', 'intPlusOne', 'intMinusOne', 85 | 'intRandom', 86 | 'floatPositiveInfinity', 'floatNegativeInfinity', 'floatNaN', 87 | 'floatMax', 'floatMin', 88 | 'floatPlusZero', 'floatMinusZero', 'floatPlusOne', 'floatMinusOne', 89 | 'floatRandomSmall', 'floatRandomAny', 90 | ), 91 | 'string' => array( 92 | 'stringEmpty', 'stringLarge', 'stringSpecial', 'stringNormal', 93 | ), 94 | 'array' => array( 95 | 'arrayEmpty', 'arrayLarge', 'arrayStrange', 96 | ), 97 | 'fileResource' => array( 98 | 'fileResourceTemp', 99 | ), 100 | 'callback' => array( 101 | 'callbackInvalid', 'callbackStrlen', 'callbackClosure', 'callbackByRef', 102 | ), 103 | 'bool' => array( 104 | 'boolTrue', 'boolFalse', 105 | ) 106 | ); 107 | 108 | // How many calls to generate 109 | $n = 50; 110 | 111 | // Which functions to use 112 | $functions = get_defined_functions(); 113 | // no userland functions 114 | $functions = $functions["internal"]; 115 | // banned functions 116 | $functions = array_diff($functions, array( 117 | // can take lots of time 118 | 'sleep', 'usleep', 'time_nanosleep', 'time_sleep_until', 119 | 'pcntl_sigwaitinfo', 'pcntl_sigtimedwait', 120 | 'readline', 'readline_read_history', 121 | 'dns_get_record', 122 | // we don't want anybody to get killed... 123 | 'posix_kill', 'pcntl_alarm', 124 | // not supported anymore as of PHP 5.4, will only throw a fatal error 125 | 'set_magic_quotes_runtime', 126 | // we already know that this function leaks, so disable it until the leak 127 | // is fixed 128 | 'readline_callback_handler_install', 129 | )); 130 | 131 | // Prefixes of functions operating on specific resources 132 | $resourceFunctionPrefixes = array( 133 | 'ftp', 'socket', 'proc', 'sem', 'shm', 'xml', 'xmlwriter', 'xmlrpc', 134 | ); 135 | --------------------------------------------------------------------------------