简介
这个漏洞源于在玄武实验室的一个漏洞信息中链接
不久前其实看过Buffalo这个品牌的设备,看的时候对其中一些机理就感觉到很奇怪,例如和文章中引起作者好奇的没有cookie这个字段的问题一样,在我看过的设备里其实也没有这个字段,当时也感觉很奇怪。这篇文章作者分析清楚了整个逻辑,整体的挖掘利用流程也确实令人拍手叫绝。
这个设备的官方下载固件包是加密的,设备价格太贵,我到底也没有成功获取作者提到的固件二进制细节进行分析,但是作者的整体流程着实值得好好学习参考,根据链接,ASUS的DSL-AC68VG这个设备的5.00.08 build272固件也受影响,因此尝试基于这个设备固件开始了一些分析。
由于作者的分析过程很值得好好学习一下,因此在本篇文章中,我会首先尝试将作者的分析过程半翻译半带自己的思考叙述一遍,然后在简单描述一下自己针对ASUS的DSL-AC68VG这个设备的分析流程,自己针对ASUS的DSL-AC68VG这个设备分析过程中取了一些巧(偷了一些懒),因此主要的二进制分析流程还是放在针对作者分析过程的理解中。
作者针对Buffalo WSR-2533DHP3研究流程
通过uart获取文件系统
作者获取设备文件系统的方法是买了一个设备,然后通过焊接出ttl端口,进而获得了root shell的方法,最终拿到了设备文件系统。
这个过程在IoT研究中其实是很常规的,通过硬件接线的方法获取设备文件系统也是处理加密固件的常见方法之一。
大概过程就是拆解开设备面板,找到并明确ttl对应的gnd, vcc, tx, rx几个接口,使用usb转ttl接线就可以获取设备启动信息,在部分设备中直接就可以获取root shell。
这个过程我个人是习惯查看官方文档或者使用万用表来判定几个接口定义的,在文章中,作者使用了JTAGulator这样一个工具,使用这个工具的好处是不仅可以明确几个接口定义,还可以通过遍历的方法快速拿到波特率的值。
明确Httpd 和 Web 界面身份验证
同样是常规思路,作者使用ghidra开始你想httpd这个二进制,作者在程序中找到了evaluate_access()这个函数。
这一步我猜作者是通过函数的名字以及函数中printf函数带的字符串,猜evaluate_access()这个函数是用来做访问限制的函数
其中函数FUN_0041f9d0()是用来判定当前发出请求的IP是否与有效登陆的IP相匹配,然后查看evaluate_access()这个函数的引用,发现被函数process_request()调用,
可以看到45-48行,这个if是用来判定是否通过认证,因为逻辑语句中间是OR,因此意味着只要bypass_check(__dest)的结果不为0,即可成功跳过认证,进入process_get()或者process_post() 的流程。
因此下一步来看看bypass_check(__dest)的逻辑:
使用 bypass_check() 绕过检查
右图可以看到bypass_list的具体列表,包括/login.html, /loginerror.html, /js/, /css/, /imaegs/等,这是有道理的,因为即使是未经身份验证的用户也需要能够访问这些 url。在比较时使用了strncasecmp,长度为bypass_list中字段的长度,这意味着,我们在访问http://router/images/someimage.png
这个链接时,不需要认证,所以我们可以考虑,是否尝试**/images/../<somepagehere>**
这样的url是否能绕过认证呢?通过访问**/images/..%2finfo.html**
这个url,成功的访问结果证明了猜想可行。
在后面,作者还通过一个错误验证了Referer 字段必须被正确的设置,如果设置成http://192.168.11.1/images/..%2finfo.html
也是会有错误的,我感觉这个错误其实不是很重要,所以不展开叙述了,总之在正确的设置头参数后,访问/images/..%2finfo.html
这个url,页面会正常返回:
目前为止,已经可以完成认证前的目录便利,但是发现在请求的时候
在请求中,请求url中还需要有_tn以及t两个参数正确设置才能正常访问,其实图里还有一个\,但是作者没有提,我猜这个参数存在与否可能与是否能正常访问页面没有关系。
我们通过路径遍历访问的 info.html 页面正在使用 /cgi/ 目录下的 .js 文件中的数据填充其信息表,并传递两个参数。一个是日期时间戳 ( _t ),另一个是我们试图找出的httoken。我们可以看到用于从 /cgi/ 获取信息的链接是使用URLToken()函数生成的,该函数使用函数get_token()设置httoken(在本例中为参数_tn ),但get_token()没有似乎在页面上使用的任何脚本中的任何位置定义。
在URLToken()定义的正上方,可以看到定义了enkripsi这个奇怪的字符串,通过查找这个字符串名,
// decode way
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var script = document.createElement("script");
script.type="text/javascript";
teks="";teksasli="";
enkripsi=ArcBase.decode(enkripsi);
var panjang;panjang=enkripsi.length;for (i=0;i<panjang;i++){ teks+=String.fromCharCode(enkripsi.charCodeAt(i)^3) }teksasli=unescape(teks);
script.text=teksasli;
head.insertBefore(script,head.firstChild );
head.removeChild(script);
大概含义就是运行时将以下脚本getToken.js添加到页面:
function getToken(){
var objs=document.getElementsByTagName("img");
var x;
for(var i=0,sz=objs.length;i < sz; i++){
x=objs[i].src;
if(x.indexOf("data:") ==0){
return ArcBase.decode(x.substring(78));
}
}
return "";
}
自此,找到了getToken()函数,可以看出,他是在页面中获取img字段的值,然后进行解码,最终将这个值作为httoken,在httpd二进制中可以找到一个函数,其中httoken被插入到 Ghidra 的 img 标签中
目前已经可以确定
向 Web 界面的各个部分发出 GET 和 POST 请求所需的httoken是在服务器端生成的
它们在加载时存储在任何给定页面底部的 img 标签中编码
- 然后在客户端 javascript 中对它们进行解码。
目前,已经可以成功通过从 login.html 获取的令牌向敏感页面发出请求,进行配置更改等敏感操作。
注入配置选项并启用 telnetd
其实作者这一步我感觉完全可以做到威胁更大的操作,因为针对普通的IOT设备,认证后逻辑中的命令注入漏洞还是比较常见的,作者采取了一个诸如配置选项进而开启telnetd的效果。但是作者的利用方法还是非常有趣,很值得借鉴。
作者首先尝试抓了一个ping功能的网络包,因为在常规IOT设备中,ping功能中经常出现命令注入的漏洞
然而,在buffalo这个设备中,作者采取ping功能后发现,ARC_ping_ipaddress会存储在全局配置文件中,作者发了一个数据包在ip地址后加了一个换行符以及数据Test Am I A New Line,然后再配置文件中查找,发现确实成功写入且存在。
因此作者考虑,是否可以覆盖掉一些有趣的配置,进而达到一些有意思的效果,经过查询,发现了ARC_SYS_TelnetdEnable=0这个配置,可以在设备上启用telnetd,
尚不清楚简单地使用ARC_SYS_TelnetdEnable=1注入配置文件是否可行,因为随后会在文件中出现一个冲突的设置(因为ARC_SYS_TelnetdEnable=0在配置文件中出现的低于ARC_ping_ipdaddress),但是,在 Burp Suite 中发送以下请求更改配置:
然后再发一个重启的数据包,重新启动完成后,作者发现即可成功telnet设备的23端口进而获取一个root shell。
总结一下整体的攻击流程:
从login.html这个无需认证的页面上的 img 标签中获取正确的httoken
将这些httoken与路径遍历结合使用以向apply_abstract.cgi发出有效请求
- 在对 apply_abstract.cgi 的有效请求中,注入 ARC_SYS_TelnetdEnable=1配置选项
- 发送另一个有效请求以重新启动设备
整体流程走完,设备的telnet端口即可被打开,成功连接后即可获取root shell。
结合ASUS DSL-AC68VG分析
根据官方公告,ASUS的DSL-AC68VG这个设备的5.00.08 build272固件也受影响,该版本固件很容易在官网上可以下载获得https://dlcdnets.asus.com/pub/ASUS/wireless/DSL-AC68VG/DSL-AC68VG_v5.00.08_build272.w.zip
通过binwalk即可常规解压,find命令可以快速查找到httpd程序所在位置
$ find -name httpd
./usr/sbin/httpd
定位查找绕过认证函数
拖入IDA,查找在这个设备中对应的evaluate_access 函数,通过检索%s has already granted
这个字符串很容易可以找到,并把这个函数重命名为evaluate_access
后边的步骤,偷了个懒,没有根据作者的步骤一步步回溯,我尝试在github中搜索ARC_SYS_LogEnable这个字符串,果然找到了一个相似的匹配链接
简单把源码和二进制汇编比较了一下,基本匹配,因此下面基本基于源码开展分析。通过ARC_SYS_LogEnable这个字符串,顺利找到对应函数check_auth
check_grant函数正是在检查访问的ip是否时已经授权过的ip
然后我们查看check_auth函数的引用,只找到一处引用,被evalute_access函数引用
可以看到正如作者分析,如果bypass_flag如果不为0,即可绕过check_auth的检查,
查找bypass_flag的引用,发现代码r->bypass_flag = bypass_check(r->url);
,bypass_flag是被bypass_check()这个函数赋值的,所以我们去看一下bypass_check这个函数
发现正是在检查url是否与bypass_list匹配
char *bypass_list[] = {
"/images/",
"/lang/",
// "/cgi/", hugh hide it due to security issue
"/js/",
"/css/",
"/setup_top_login.htm",
"/login.htm",
"/loginpserr.htm",
"/login.cgi",
"/3g_conn.xml",
"/top_conn.xml",
"/wireless_calibration.htm",
"/cgi/cgi_login.js",
"/cgi/cgi_autologout.js",
"/internet_paused.htm",
NULL
...
}
明晰httoken处理流程
根据作者的分析,身份验证绕过的漏洞点并不能绕过验证访问任意界面,因为在访问的时候还需要正确的httoken值。根据前面的对照,httoken是在服务器端生成,然后在前端js解码,最终将这个值添加到访问请求中
在代码中找到了以下位置
static int ssi_image_token(struct request_rec *r, int argc, char **argv)
{
int i;
int type; // runsen_lao, log type: 0--system_log 1--security_log
int nbytes = 0;
unsigned long token = 0;
// NzgyMjIwMTU2Cg==
char output_buf[OUT_BASE64_SZ+1] ={0}; // {[0 ... (OUT_BASE64_SZ + 1)] = 0x00};
char *p;
int len=0;
int tid;
if((tid = get_tid()) == MID_FAIL)
return 0;
#ifdef CONFIG_HTTPD_TOKEN_CHECK_SUPPORT
token = httoken_get( r->url?r->url:"/" );
//ht_dbg("token=%lu from %s\n", token, r->url?r->url:"NULL" );
sprintf(output_buf, "%lu", token );
#else
sprintf(output_buf, "123456789"); //hard code
#endif
len=strlen(output_buf);
p=(char *)( output_buf + len+1);
//cprintf("get [%s]\n",output_buf);
b64_encode((unsigned char *)output_buf ,len, (unsigned char *) p, OUT_BASE64_SZ-len);
// NOTE: hugh 2014/2/20
// A Data URI scheme(RFC2397) to present a space GIF icon.
//
// this return a empty gif for present purpuse! but we dirty append our session ID after
// UI need take time to reteieve the sesison ID from "dutrefer" Image Object
//
nbytes += so_printf(r, "<img title=spacer src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7%s\" border=0>", p);
return nbytes;
}
在前端源码中也找到了运行时会添加到页面的代码,这段代码会将服务器端生成的码解码,然后添加到新的请求当中去。
function getToken(){
var objs=document.getElementsByTagName("img");
var x;
for(var i=0,sz=objs.length;i < sz; i++){
x=objs[i].src;
if(x.indexOf("data:") ==0){
return ArcBase.decode(x.substring(78));
}
}
return "";
}
总结
本次分析中,没有针对ASUS DSL-AC68VG设备固件进行很详细的分析,借着作者的思路明确这个设备的漏洞利用流程其实也是比较容易的,但是个人认为重要的是作者的研究思路很值得参考,所以主要分析了一下作者的挖掘流程,并加入了一些自己的思考(当然,主要原因还是因为懒)。
从访问页面没有cookie头信息到去探析整个认证授权流程,然后梳理清楚httoken的产生流程,到最后通过在配置中新加入一项配置来达到攻击效果的攻击手段,都很值得借鉴。