首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第16期->技术专题
期刊号: 类型: 关键词:
apache的..%5c漏洞原因源代码分析

作者:yuange < yuange@nsfocus.com >
主页:http://www.nsfocus.com
日期:2000-12-14

我们来看看apache出错原因。

   下面是源代码 src\main\http_request.c: 的处理请求的代码:


static void process_request_internal(request_rec *r)
{
    int access_status;

    /* Ignore embedded %2F's in path for proxy requests */
    if (r->proxyreq == NOT_PROXY && r->parsed_uri.path) {
/* 如果不是代理  */
    access_status = ap_unescape_url(r->parsed_uri.path);
/* 解码%,rfc要求 */
    if (access_status) {
        ap_die(access_status, r);
        return;
    }
    }

    ap_getparents(r->uri);     /* OK --- shrinking transformations... */
/* 处理/../,出错,没考虑windows下面的\..\ */

    if ((access_status = location_walk(r))) {
        ap_die(access_status, r);
        return;
    }

    if ((access_status = ap_translate_name(r))) {
        decl_die(access_status, "translate", r);
        return;
    }

    if (r->proxyreq == NOT_PROXY) {
    /*
     * We don't want TRACE to run through the normal handler set, we
     * handle it specially.
     */
    if (r->method_number == M_TRACE) {
        if ((access_status = ap_send_http_trace(r)))
        ap_die(access_status, r);
        else
        ap_finalize_request_protocol(r);
        return;
    }
    }

    if (r->proto_num > HTTP_VERSION(1,0) && ap_table_get(r->subprocess_env, "downgrade-1.0")) {
        r->proto_num = HTTP_VERSION(1,0);
    }

    /*
     * NB: directory_walk() clears the per_dir_config, so we don't inherit
     * from location_walk() above
     */

    if ((access_status = directory_walk(r))) {
        ap_die(access_status, r);
        return;
    }

    if ((access_status = file_walk(r))) {
        ap_die(access_status, r);
        return;
    }

    if ((access_status = location_walk(r))) {
        ap_die(access_status, r);
        return;
    }

    if ((access_status = ap_header_parse(r))) {
        ap_die(access_status, r);
        return;
    }

    switch (ap_satisfies(r)) {
    case SATISFY_ALL:
    case SATISFY_NOSPEC:
        if ((access_status = ap_check_access(r)) != 0) {
            decl_die(access_status, "check access", r);
            return;
        }
        if (ap_some_auth_required(r)) {
            if (((access_status = ap_check_user_id(r)) != 0) || !ap_auth_type(r)) {
                decl_die(access_status, ap_auth_type(r)
            ? "check user.  No user file?"
            : "perform authentication. AuthType not set!", r);
                return;
            }
            if (((access_status = ap_check_auth(r)) != 0) || !ap_auth_type(r)) {
                decl_die(access_status, ap_auth_type(r)
            ? "check access.  No groups file?"
            : "perform authentication. AuthType not set!", r);
                return;
            }
        }
        break;
    case SATISFY_ANY:
        if (((access_status = ap_check_access(r)) != 0) || !ap_auth_type(r)) {
            if (!ap_some_auth_required(r)) {
                decl_die(access_status ? access_status :
             HTTP_INTERNAL_SERVER_ERROR,
             ap_auth_type(r) ? "check access"
            : "perform authentication. AuthType not set!", r);
                return;
            }
            if (((access_status = ap_check_user_id(r)) != 0) || !ap_auth_type(r)) {
                decl_die(access_status, ap_auth_type(r)
            ? "check user.  No user file?"
            : "perform authentication. AuthType not set!", r);
                return;
            }
            if (((access_status = ap_check_auth(r)) != 0) || !ap_auth_type(r)) {
                decl_die(access_status, ap_auth_type(r)
            ? "check access.  No groups file?"
            : "perform authentication. AuthType not set!", r);
                return;
            }
        }
        break;
    }

    if (! (r->proxyreq != NOT_PROXY
       && r->parsed_uri.scheme != NULL
       && strcmp(r->parsed_uri.scheme, "http") == 0) ) {
    if ((access_status = ap_find_types(r)) != 0) {
        decl_die(access_status, "find types", r);
        return;
    }
    }

    if ((access_status = ap_run_fixups(r)) != 0) {
        ap_die(access_status, r);
        return;
    }

    if ((access_status = ap_invoke_handler(r)) != 0) {
        ap_die(access_status, r);
        return;
    }

    /* Take care of little things that need to happen when we're done */
    ap_finalize_request_protocol(r);
}


下面是源代码 src\main\util.c 处理/../的代码:


API_EXPORT(void) ap_getparents(char *name)
{
    int l, w;

    /* Four paseses, as per RFC 1808 */
    /* a) remove ./ path segments */

    for (l = 0, w = 0; name[l] != '\0';) {
    if (name[l] == '.' && name[l + 1] == '/' && (l == 0 || name[l - 1] == '/'))
        l += 2;
    else
        name[w++] = name[l++];
    }

    /* b) remove trailing . path, segment */
    if (w == 1 && name[0] == '.')
    w--;
    else if (w > 1 && name[w - 1] == '.' && name[w - 2] == '/')
    w--;
    name[w] = '\0';

    /* c) remove all xx/../ segments. (including leading ../ and /../) */
    l = 0;

    while (name[l] != '\0') {
    if (name[l] == '.' && name[l + 1] == '.' && name[l + 2] == '/' &&
        (l == 0 || name[l - 1] == '/')) {
        register int m = l + 3, n;

        l = l - 2;
        if (l >= 0) {
        while (l >= 0 && name[l] != '/')
            l--;
        l++;
        }
        else
        l = 0;
        n = l;
        while ((name[n] = name[m]))
        (++n, ++m);
    }
    else
        ++l;
    }

    /* d) remove trailing xx/.. segment. */
    if (l == 2 && name[0] == '.' && name[1] == '.')
    name[0] = '\0';
    else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.' && name[l - 3] == '/') {
    l = l - 4;
    if (l >= 0) {
        while (l >= 0 && name[l] != '/')
        l--;
        l++;
    }
    else
        l = 0;
    name[l] = '\0';
    }
}

    大家看到了,显然没有考虑windows下面的“\..\”,看来编写这个函数的人不熟悉WINDOWS的特性,是个长期在*UNIX下面编程的人员。其实没考虑WINDOWS下面的“\..\”已经出过很多问题的了。那为什么直接telnet 用“\..\”不行呢?看来在解码之前把所有“\”替换成了“/”。显然这个转换是该在解码之后,估计上面没有检测“\..\”的原因就是认为先已经转换了,这个就与IIS的%c1%1c漏洞一样。照这样就一定得注意如果有什么编码、解码的,一定得重新考虑前面的一些检测,因为弄不好就能重新编码出不符合前面检测的东西来。


   看这是源代码 src\main\http_protocol.c 中将URI中的“\”转化成“/”的处理模块:

/* parse_uri: break apart the uri
* Side Effects:
* - sets r->args to rest after '?' (or NULL if no '?')
* - sets r->uri to request uri (without r->args part)
* - sets r->hostname (if not set already) from request (scheme://host:port)
*/
CORE_EXPORT(void) ap_parse_uri(request_rec *r, const char *uri)
{
    int status = HTTP_OK;

    r->unparsed_uri = ap_pstrdup(r->pool, uri);

    if (r->method_number == M_CONNECT) {
    status = ap_parse_hostinfo_components(r->pool, uri, &r->parsed_uri);
    } else {
    /* Simple syntax Errors in URLs are trapped by parse_uri_components(). */
    status = ap_parse_uri_components(r->pool, uri, &r->parsed_uri);
    }

    if (ap_is_HTTP_SUCCESS(status)) {
    /* if it has a scheme we may need to do absoluteURI vhost stuff */
    if (r->parsed_uri.scheme
        && !strcasecmp(r->parsed_uri.scheme, ap_http_method(r))) {
        r->hostname = r->parsed_uri.hostname;
    } else if (r->method_number == M_CONNECT) {
        r->hostname = r->parsed_uri.hostname;
    }
    r->args = r->parsed_uri.query;
    r->uri = r->parsed_uri.path ? r->parsed_uri.path
                    : ap_pstrdup(r->pool, "/");
#if defined(OS2) || defined(WIN32)
    /* Handle path translations for OS/2 and plug security hole.
     * This will prevent "http://www.wherever.com/..\..\/" from
     * returning a directory for the root drive.
     */
    {
        char *x;

        for (x = r->uri; (x = strchr(x, '\\')) != NULL; )
        *x = '/';
/* \转换成/ */

    }
#endif  /* OS2 || WIN32 */
    }
    else {
    r->args = NULL;
    r->hostname = NULL;
    r->status = status;             /* set error status */
    r->uri = ap_pstrdup(r->pool, uri);
    }
}

    
    这是上面那文章总结第一条的源代码分析。
版权所有,未经许可,不得转载