[Tips]apache mod_proxy简要分析
author: yunshu#ph4nt0m.org
date: 2007-9-6
http://www.ph4nt0m.org
Text Mode
这几天要连续培训5天,下午的时候在会场快闷死了。还好看到mod_proxy的漏洞公告,就下载apache的代码看了看,度过了漫长的听人废话的时间。大致的过程如下:
首先看漏洞公告说的,漏洞出现在ap_proxy_date_canon函数里面,公告这这里,http://secunia.com/advisories/26636/。直接搜索函数名,定位到如下地址:modules/proxy/proxy_util.c的第293行,代码比较长:
代码:
PROXY_DECLARE(const char *)
ap_proxy_date_canon(apr_pool_t *p, const char *x1)
{
char *x = apr_pstrdup(p, x1);
int wk, mday, year, hour, min, sec, mon;
char *q, month[4], zone[4], week[4];
q = strchr(x, ',');
/* check for RFC 850 date */
if (q != NULL && q - x > 3 && q[1] == ' ') {
*q = ' \ 0 ';
for (wk = 0; wk < 7; wk++)
if (strcmp(x, lwday[wk]) == 0)
break;
*q = ',';
if (wk == 7)
return x; /* not a valid date */
if (q[4] != '-' || q[8] != '-' || q[11] != ' ' || q[14] != ':' ||
q[17] != ':' || strcmp(&q[20], " GMT") != 0)
return x;
if (sscanf(q + 2, "%u-%3s-%u %u:%u:%u %3s", &mday, month, &year,
&hour, &min, &sec, zone) != 7)
return x;
if (year < 70)
year += 2000;
else
year += 1900;
}
else {
/* check for acstime() date */
if (x[3] != ' ' || x[7] != ' ' || x[10] != ' ' || x[13] != ':' ||
x[16] != ':' || x[19] != ' ' || x[24] != ' \ 0 ')
return x;
if (sscanf(x, "%3s %3s %u %u:%u:%u %u", week, month, &mday, &hour,
&min, &sec, &year) != 7)
return x;
for (wk = 0; wk < 7; wk++)
if (strcmp(week, apr_day_snames[wk]) == 0)
break;
if (wk == 7)
return x;
}
/* check date */
for (mon = 0; mon < 12; mon++)
if (strcmp(month, apr_month_snames[mon]) == 0)
break;
if (mon == 12)
return x;
q = apr_palloc(p, 30);
apr_snprintf(q, 30, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", apr_day_snames[wk],
mday, apr_month_snames[mon], year, hour, min, sec);
return q;
}
ap_proxy_date_canon(apr_pool_t *p, const char *x1)
{
char *x = apr_pstrdup(p, x1);
int wk, mday, year, hour, min, sec, mon;
char *q, month[4], zone[4], week[4];
q = strchr(x, ',');
/* check for RFC 850 date */
if (q != NULL && q - x > 3 && q[1] == ' ') {
*q = ' \ 0 ';
for (wk = 0; wk < 7; wk++)
if (strcmp(x, lwday[wk]) == 0)
break;
*q = ',';
if (wk == 7)
return x; /* not a valid date */
if (q[4] != '-' || q[8] != '-' || q[11] != ' ' || q[14] != ':' ||
q[17] != ':' || strcmp(&q[20], " GMT") != 0)
return x;
if (sscanf(q + 2, "%u-%3s-%u %u:%u:%u %3s", &mday, month, &year,
&hour, &min, &sec, zone) != 7)
return x;
if (year < 70)
year += 2000;
else
year += 1900;
}
else {
/* check for acstime() date */
if (x[3] != ' ' || x[7] != ' ' || x[10] != ' ' || x[13] != ':' ||
x[16] != ':' || x[19] != ' ' || x[24] != ' \ 0 ')
return x;
if (sscanf(x, "%3s %3s %u %u:%u:%u %u", week, month, &mday, &hour,
&min, &sec, &year) != 7)
return x;
for (wk = 0; wk < 7; wk++)
if (strcmp(week, apr_day_snames[wk]) == 0)
break;
if (wk == 7)
return x;
}
/* check date */
for (mon = 0; mon < 12; mon++)
if (strcmp(month, apr_month_snames[mon]) == 0)
break;
if (mon == 12)
return x;
q = apr_palloc(p, 30);
apr_snprintf(q, 30, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", apr_day_snames[wk],
mday, apr_month_snames[mon], year, hour, min, sec);
return q;
}
粗略的看一遍,没发现啥大的问题。决定先看看apr_pstrdup函数,因为从这上面的代码来看,apr_pstrdup应该是在分配内存。这里有一个小的tips,怎么样搜索到函数的定义。
代码:
grep -r apr_pstrdup * | grep -v "=" | grep -v "return" | grep -v "Binary" | grep -v "(apr_pstrdup" | grep -v ", apr_pstrdup" | grep -v ";"
首先在当前目录和子目录中查找所有包含这个字符串的行,然后去掉有=的,去掉有return的,去掉二进制文件,去掉前面有(的,去掉前面有,空格的,去掉有;的,剩下的就是函数的定义了。这个查找根据不同的代码风格,灵活的去写。
apr_pstrdup函数是在srclib/apr/strings/apr_strings.c中的第69行定义的,代码很简单,就是拷贝了一下字符串,而且注意了长度,没什么还利用的。只是其中又调用了另外一个函数apr_palloc,利用上面同样的办法,定位到 srclib/apr/memory/unix/apr_pools.c的594行,这个代码比较复杂,我没有仔细去看就判断它没问题。因为这个是很底层的内存池的函数,调用很频繁,如果有安全问题就不会仅仅是ap_proxy_date_canon出现问题了,而是整个apache都会有问题。
这样看来,想在ap_proxy_date_canon中找个可利用的溢出是不可能的了,唯一可能的地方是apr_palloc(p, 30)这里,但是下面用apr_snprintf做了限制,于是就开始看别的方面。
最终发现函数中有这样的片段:
代码:
char *q, month[4], zone[4], week[4];
q = strchr(x, ',');
/* check for RFC 850 date */
if (q != NULL && q - x > 3 && q[1] == ' ') {
*q = ' \ 0 ';
for (wk = 0; wk < 7; wk++)
if (strcmp(x, lwday[wk]) == 0)
break;
*q = ',';
if (wk == 7)
return x; /* not a valid date */
if (q[4] != '-' || q[8] != '-' || q[11] != ' ' || q[14] != ':' ||
q[17] != ':' || strcmp(&q[20], " GMT") != 0)
return x;
..
q = strchr(x, ',');
/* check for RFC 850 date */
if (q != NULL && q - x > 3 && q[1] == ' ') {
*q = ' \ 0 ';
for (wk = 0; wk < 7; wk++)
if (strcmp(x, lwday[wk]) == 0)
break;
*q = ',';
if (wk == 7)
return x; /* not a valid date */
if (q[4] != '-' || q[8] != '-' || q[11] != ' ' || q[14] != ':' ||
q[17] != ':' || strcmp(&q[20], " GMT") != 0)
return x;
..
这里取出了时间之后,通过确定,的位置来判断时间的格式,但是后面没有计算q的长度就直接读取q[8],q[11]等等,类似Last-Modified字段出现Wed, 05 Sep这样的字符串,会导致数组读取越界。这个是从代码推测的,我并没有去测试。
现在看看这个函数是如何调用的,先从如何写apache的mod入手。首先申明结构体module AP_MODULE_DECLARE_DATA,这个结构体的最后一个成员指针,指向了该module的注册函数。在注册函数里面关联需要处理的时间,触发功能函数。mod_proxy结构如下:
代码:
static void ap_proxy_http_register_hook(apr_pool_t *p)
{
proxy_hook_scheme_handler(proxy_http_handler, NULL, NULL, APR_HOOK_FIRST);
proxy_hook_canon_handler(proxy_http_canon, NULL, NULL, APR_HOOK_FIRST);
}
module AP_MODULE_DECLARE_DATA proxy_http_module = {
STANDARD20_MODULE_STUFF,
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
NULL, /* command apr_table_t */
ap_proxy_http_register_hook/* register hooks */
};
{
proxy_hook_scheme_handler(proxy_http_handler, NULL, NULL, APR_HOOK_FIRST);
proxy_hook_canon_handler(proxy_http_canon, NULL, NULL, APR_HOOK_FIRST);
}
module AP_MODULE_DECLARE_DATA proxy_http_module = {
STANDARD20_MODULE_STUFF,
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
NULL, /* command apr_table_t */
ap_proxy_http_register_hook/* register hooks */
};
注册了proxy_http_handler函数和proxy_http_canon函数来处理一些请求。最终有问题函数的调用流程如下:
proxy_http_handler---->ap_proxy_http_process_response---->ap_proxy_read_headers---->process_proxy_header---->ap_proxy_date_canon
这样看来,只是在apache作为正向代理或者反向代理,处理real server返回的数据,在解析http头中的"Date", "Expires", "Last-Modified"等三个字段的时候,遇到畸形的时间结构,mod_proxy模块发生数组访问越界。就攻击而言,反向代理无法攻击,除非能控制到real server。如果是正向代理,到是可以自己伪造一个web服务器,然后设置apache为代理去访问伪造的web server,返回畸形的时间头给apache代理尝试攻击。
总的来说,这个漏洞没多大意义,不过也许是我看错了,呵呵。
没有评论:
发表评论