CVE-2018-8715 分析【通过】

CVE-2018-8715

环境搭建

利用vulnhub的环境
docker-compose up -d后台运行,端口设置不要改,默认8080

复现

Appweb简介

Appweb是一个嵌入式HTTP Web服务器,主要的设计思路是安全。这是直接集成到客户的应用和设备,便于开发和部署基于Web的应用程序和设备。它迅速( 每秒处理3500多要求)而紧凑 ,其中包括支持动态网页制作,服务器端嵌入式脚本过程中的CGI ,可加载模块的SSL ,摘要式身份验证,虚拟主机, Apache样式配置,日志记录,单和多线程应用程序。它提供了大量的文档和示例。

AppWeb是Embedthis Software LLC公司负责开发维护的一个基于GPL开源协议的嵌入式Web Server。他使用C/C++来编写,能够运行在几乎先进所有流行的操作系统上。当然他最主要的应用场景还是为嵌入式设备提供Web Application容器。

AppWeb可以进行认证配置,其认证方式包括以下三种:

  • basic 传统HTTP基础认证
  • digest 改进版HTTP基础认证,认证成功后将使用Cookie来保存状态,而不用再传递Authorization头
  • form 表单认证

漏洞原理及复现

我复现的时候并没有如他人所说需要重置session,直接添加:Authorization: Digest username=joshua

正常步骤是:
在添加了Authorization: Digest username=joshua之后,因为我们没有传入密码字段,所以服务端出现错误,直接返回了200,且包含一个session,于是再附上session

原理

其7.0.3之前的版本中,对于digest和form两种认证方式,如果用户传入的密码为null(也就是没有传递密码参
数),appweb将因为一个逻辑错误导致直接认证成功,并返回session。

漏洞位置在appweb/paks/http/dist/httpLib.c

首先是function authCondition()

/*
    This condition is used to implement all user authentication for routes
 */
static int authCondition(HttpConn *conn, HttpRoute *route, HttpRouteOp *op)
{
    HttpAuth    *auth;
    cchar       *username, *password;

    assert(conn);
    assert(route);

    auth = route->auth;
    if (!auth || !auth->type) {
        /* Authentication not required */
        return HTTP_ROUTE_OK;
    }
    if (!httpIsAuthenticated(conn)) {
        httpGetCredentials(conn, &username, &password);
        if (!httpLogin(conn, username, password)) {
            if (!conn->tx->finalized) {
                if (auth && auth->type) {
                    (auth->type->askLogin)(conn);
                } else {
                    httpError(conn, HTTP_CODE_UNAUTHORIZED, "Access Denied, login required");
                }
                /* Request has been denied and a response generated. So OK to accept this route. */
            }
            return HTTP_ROUTE_OK;
        }
    }
    if (!httpCanUser(conn, NULL)) {
        httpTrace(conn, "auth.check", "error", "msg:'Access denied, user is not authorized for access'");
        if (!conn->tx->finalized) {
            httpError(conn, HTTP_CODE_FORBIDDEN, "Access denied. User is not authorized for access.");
            /* Request has been denied and a response generated. So OK to accept this route. */
        }
    }
    /* OK to accept route. This does not mean the request was authenticated - an error may have been already generated */
    return HTTP_ROUTE_OK;
}

这个函数负责调用两个用于认证处理的函数:getCredentialshttpLogin,注意到httpGetCredentials周围缺少检查,这将会在稍后起到作用

httpGetCredentials():

/*
    Get the username and password credentials. If using an in-protocol auth scheme like basic|digest, the
    rx->authDetails will contain the credentials and the parseAuth callback will be invoked to parse.
    Otherwise, it is expected that "username" and "password" fields are present in the request parameters.
    This is called by authCondition which thereafter calls httpLogin
 */
PUBLIC bool httpGetCredentials(HttpConn *conn, cchar **username, cchar **password)
{
    HttpAuth    *auth;

    assert(username);
    assert(password);
    *username = *password = NULL;

    auth = conn->rx->route->auth;
    if (!auth || !auth->type) {
        return 0;
    }
    if (auth->type) {
        if (conn->authType && !smatch(conn->authType, auth->type->name)) {
            if (!(smatch(auth->type->name, "form") && conn->rx->flags & HTTP_POST)) {
                /* If a posted form authentication, ignore any basic|digest details in request */
                return 0;
            }
        }
        if (auth->type->parseAuth && (auth->type->parseAuth)(conn, username, password) < 0) {
            return 0;
        }
    } else {
        *username = httpGetParam(conn, "username", 0);
        *password = httpGetParam(conn, "password", 0);
    }
    return 1;
}

该函数接收两个指向数组的指针用于从请求中解析usernamepassword。既然authCondition没有进行参数检查,那么parseAuth失败也无关紧要。这意味着我们能够插入WWW-Authenticate header或者post任何我们想要的认证数据

httpLogin()

/*
    Login the user and create an authenticated session state store
 */
PUBLIC bool httpLogin(HttpConn *conn, cchar *username, cchar *password)
{
    HttpRx          *rx;
    HttpAuth        *auth;
    HttpSession     *session;
    HttpVerifyUser  verifyUser;

    rx = conn->rx;
    auth = rx->route->auth;
    if (!username || !*username) {
        httpTrace(conn, "auth.login.error", "error", "msg:'missing username'");
        return 0;
    }
    if (!auth->store) {
        mprLog("error http auth", 0, "No AuthStore defined");
        return 0;
    }
    if ((verifyUser = auth->verifyUser) == 0) {
        if (!auth->parent || (verifyUser = auth->parent->verifyUser) == 0) {
            verifyUser = auth->store->verifyUser;
        }
    }
    if (!verifyUser) {
        mprLog("error http auth", 0, "No user verification routine defined on route %s", rx->route->pattern);
        return 0;
    }
    if (auth->username && *auth->username) {
        /* If using auto-login, replace the username */
        username = auth->username;
        password = 0;
    }
    if (!(verifyUser)(conn, username, password)) {
        return 0;
    }
    if (!(auth->flags & HTTP_AUTH_NO_SESSION) && !auth->store->noSession) {
        if ((session = httpCreateSession(conn)) == 0) {
            /* Too many sessions */
            return 0;
        }
        httpSetSessionVar(conn, HTTP_SESSION_USERNAME, username);
        httpSetSessionVar(conn, HTTP_SESSION_IP, conn->ip);
    }
    rx->authenticated = 1;
    rx->authenticateProbed = 1;
    conn->username = sclone(username);
    conn->encoded = 0;
    return 1;
}

这一函数会检查username是否为空,当session已被分配时,password指针可以为空

为了能够绕过身份验证,我们需要能够传递空密码指针,幸运的是,对于表单和摘要身份验证,用于解析身份验证详细信息的函数(第1666行)将允许我们设置空密码指针,并且即使返回错误,最终也不会被authCondition检查,允许我们完全绕过身份验证,利用这个的唯一条件是知道hashmap中的用户名。

为了克服这个限制,必须考虑到散列映射的大小通常很小,并且散列映射中使用的散列算法(FNV)很弱:尝试次数有限,可能会发现冲突,并且登录不知道有效的用户名(未经测试)

reference

https://blog.csdn.net/weixin_42936566/article/details/87120710
https://ssd-disclosure.com/archives/3676

  • 通过
  • 未通过

0 投票者

1 Like