简介
mini_httpd是一个小型的HTTP服务器,它的代码非常小巧,仅有几千行代码,因此它适用于嵌入式设备和低功耗系统。它是由Jef Poskanzer开发的,可以在多种操作系统上运行,包括Linux、FreeBSD、Solaris、Windows等。mini_httpd支持动态内容的生成,包括CGI、SSI以及FastCGI,同时它也支持虚拟主机和基本的身份验证。mini_httpd的主要特点是快速、轻量级、安全和易于使用。
mini_httpd的代码下载地址:mini_httpd
数据包处理逻辑
mini_httpd收到一个数据请求包后会fork创建一个子进程来进行处理,这种方式如果在高并发场景会在进程创建、销毁过程中消耗大量的资源,但是在并发量低的嵌入式设备已经够用了。
1 | /* Fork a sub-process to handle the connection. */ |
子进程中主要是通过handle_request函数来对数据进行处理的,主要是先解析请求行、再解析请求头。
- 读取请求的第一行,获取到请求行,然后从行中解析到请求方法protocol、请求路径path和查询参数query
- 随后解析header,主要实现是通过while循环继续逐行解析header中的字段,包括Authorization、Content-Length、Content-Type、Cookie、User-Agent等等常见的字段
如下是handle_request函数中读取请求行,获取到请求method_str、path、query、protocol。
1 | /* Parse the first line of the request. */ |
然后是while循环处理header,获取字段。在源码中包括:Authorization、Content-Length、Content-Type、Cookie、Host、If-Modified-Since、Referer、Referrer、User-Agent这些字段。
1 | /* Parse the rest of the request headers. */ |
获取到了如上的重要字段后,就开始对数据包的合法性进行判断,例如:
- 请求方法method是否合理:GET、HEAD、POST、PUT、DELETE、TRACE
- 请求路径path必须以反斜杠
/
开头、对path进行目录穿越相关字符进行处理、检查文件是否存在 - 然后根据path是文件夹或文件,分别调用do_dir和do_file进行处理,二者最终都会进行权限检查函数auth_check
在权限检查函数auth_check中,输入为请求的path转换的实际路径file所在的文件夹dirname,如果权限检查通过,则继续直接随后的数据包处理流程;如果权限检查是否,则通过send_authenticate函数返回401,然后结束当前连接的生命周期。
权限检查的流程则是:
- 如果dirname中没有.htpasswd文件,那么直接认证通过。就相当于是在需要授权访问的文件夹中添加该文件,不需要授权访问的文件夹中没有该文件
- 源码中采用的校验方式是BASIC认证,请求包中带上username和base64编码的password,然后和.htpasswd文件中保存的账号信息进行对比,如果比较通过则直接返回。
漏洞挖掘思路
因此,平常漏洞挖掘中比较关心的登录认证流程就非常清晰:main -> handle_request -> do_file/do_dir -> auth_check。一般情况下,厂商会根据自己的业务逻辑修改相关的函数,但是根据源码我们还是能通过一些字符串特征来定位到关键函数,例如:
- 通过搜索index相关的页面字符串,可以定位到handle_request
1
2
3
4
5
6
7.data:0041E030 index_names: .word aSetupCgi # DATA XREF: handle_request+38↑o
.data:0041E030 # "setup.cgi"
.data:0041E034 .word aIndexHtml # "index.html"
.data:0041E038 .word aIndexHtm # "index.htm"
.data:0041E03C .word aIndexXhtml # "index.xhtml"
.data:0041E040 .word aIndexXht # "index.xht"
.data:0041E044 .word aDefaultHtm # "Default.htm" - 函数handle_request中的逻辑是由开发者定义,因此可能发生缓冲区溢出、命令注入等常见漏洞形式
- 通过搜索字符串.htpasswd可以直接定位到do_file、auth_check函数。do_file函数中会检查请求文件是否为.htpasswd,auth_check函数则是需要读取账号信息、调用字符串比较函数等
经典案例
案例1:发生在auth_check中的认证绕过
发生在netgear wac104设备、固件版本1.0.4.15之前的身份认证绕过漏洞,漏洞产生的原因是在鉴权过程中,使用了strstr来判断:如果请求uri中包含currentsetting.htm,设置无需认证标志。因此攻击者可以在需要鉴权的uri中包含currentsetting.htm标志,从而达到认证绕过的目的。
通过之前的源代码梳理,也明白了mini_httpd的登录认证流程,那么可以通过搜索字符串的技巧直接定位到auth_check函数。auth_check函数开头有一段导致后续认证绕过的逻辑,其中有一个g_bypass_flag=1时可以直接通过认证。
1 | if ( g_bypass_flag == 1 ) |
查看变量g_bypass_flag的交叉引用,赋值的地方一共包含如下的三处:
- 当请求path中包含currentsetting.htm的时候
1
2if ( strstr(v86, "currentsetting.htm") )
g_bypass_flag = 1; - SOAPAction相关,设计的初衷应该是可以访问任意SOAP的xml。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19else
{
v26 = strncasecmp(v34, "Accept-Language:", 16);
v27 = v34;
if ( v26 )
{
v30 = v34 + 11;
if ( !strncasecmp(v27, "SOAPAction:", 11) )
{
v31 = strspn(v30, " \t");
v32 = strcasestr(&v30[v31], "urn:NETGEAR-ROUTER:service:");
......
if ( v32 )
{
......
g_bypass_flag = 1;
}
}
} - 请求path中包含setupwizard.cgi,但是随后的处理逻辑会调用exit退出,因此无法利用。这个可能是当设备首次启动、开始安装向导触发的。
1
2if ( strstr((const char *)g_path, "setupwizard.cgi") )
g_bypass_flag = 1;
再次返回到mini_httpd的源代码中,结合固件中的反汇编
- 首先通过查找method后的第一个空格、换行、制表符的方式,获取到path。但是随后没有对path中是否包含%00进行判断。
1
2v10 = strpbrk(v8, " \t\n\r");
g_path = v10; - 获取到的path在内存中大概是:
uri\0currentsetting.htm
,这导致,strstr函数返回一个非空值,就设置了g_bypass_flag,从而通过了auth_check1
2
3
4
5
6......
v86 = (const char *)g_path;
......
if ( strstr(v86, "currentsetting.htm") )
g_bypass_flag = 1;
......
案例2:发生在函数handle_request
CVE-2021-34979
发生在NETGEAR R6260,固件版本V1.1.0.78_1.0.1中,处理
SOAPAction
标头由于未判断全局数组spapServiceName
的边界,导致越界写。写入的数据会以环境变量的形式传递到setupwizard.cgi中,进而造成缓冲区溢出。
越界写:发生在处理数据包的函数handle_request
中,未判断边界
1 | else if (strncasecmp(line, "SOAPAction:", 11) == 0) |
后续调用setupwizard.cgi时,环境变量会传入,并且造成缓冲区溢出。
1 | bool check_soap_login_record() |
小结
mini_httpd主要容易发生漏洞的地方就是在处理数据包的函数handle_request处,因为此处是开发者主要添加自己代码的地方,例如对header的处理、认证的自我实现方式等等。