概述:
在对某些厂家的IOT网关设备进行检测时,发现了一个RCE漏洞,这个漏洞存在于大多数网关路由器设备中,漏洞点位于更新ntp中。这些漏洞的利用是需要条件的,那就是首先得登录(当然不排除有未授权的情况发生),个人感觉这个漏洞比较好玩,所以,就发出来分享一下,给大家提供一个思路。
当然,如果你想测试这个漏洞,最好找以前的版本(光猫、路由器)测试,现在最新的版本大多数都已修复,如果你恰好遇到存在此漏洞的设备,那么祝君好运!
漏洞分析
在我们传入参数之前,httpd程序(http服务器)会对传过来的URL进行CGI解析,随后选择调用sntp程序并传入参数。怎么调用这个不是我们要分析的重点(虽然这个也是RCE的一部分),重点是下面的过程。
在SNTP程序文件中,它首先获取参数的传入
随后把参数传入了ntpdate文件 , bin/ntpdate ntpserver地址
在获取了ntpserver地址后,进行了系统命令调用
第一次system执行调用是初始化ntpdate程序(清理关闭干扰程序),随后system执行nptdate程序,获取当前ntp服务器的时间,加之写入配置文件。
漏洞利用
我们来看下漏洞利用点,漏洞发生在设备的时间设定功能上
因为这个漏洞是隐式RCE,所以没有返回,我们只能进系统进行验证。
根据以上分析,我们后台监控看下
可以看到,sntp成功的调用了ntpdate程序来获取时间,而且ntpserver服务器的参数是我们可以控制的。
这里我们需要输入分隔符“;”,这样,我们就能够执行多行命令了。
我们向tmp目录写入test.txt文件
后台监控看下是否利用成功
在这里,我们还是来看下前端代码吧,因为代码太长,所以选择主要函数讲解。
在我们提交保存按钮后,会调用btnApply()函数,我们跟进
function btnApply() {
var loc = 'sntpcfg.cgi?ntp_enabled=';
with( document.forms[0] ) {
if( ntpEnabled.checked ) {
loc += '1&ntpServer1=';
if( ntpServer1.selectedIndex == ntpServers.length ) {
if( ntpServerOther1.value.length == 0 ) { // == Other
alert('第一时间服务器为“其它”,可是“其它”域为空');
return;
} else {
loc += ntpServerOther1.value;
}
} else {
loc += ntpServer1[ntpServer1.selectedIndex].value;
}
loc += '&ntpServer2=';
if( ntpServer2.selectedIndex == ntpServers.length+1 ) {
if( ntpServerOther2.value.length == 0 ) { // == Other
alert('第二时间服务器为“其它”,可是“其它”域为空');
return;
} else {
loc += ntpServerOther2.value;
}
} else {
if( ntpServer2.selectedIndex > 0 )
loc += ntpServer2[ntpServer2.selectedIndex].value;
}
loc += '&ntpServer3=';
if( ntpServer3.selectedIndex == ntpServers.length+1 ) {
if( ntpServerOther3.value.length == 0 ) { // == Other
alert('第三时间服务器为“其它”,可是“其它”域为空');
return;
} else {
loc += ntpServerOther3.value;
}
} else {
if( ntpServer3.selectedIndex > 0 )
loc += ntpServer3[ntpServer3.selectedIndex].value;
}
loc += '&ntpServer4=';
if( ntpServer4.selectedIndex == ntpServers.length+1 ) {
if( ntpServerOther4.value.length == 0 ) { // == Other
alert('第四时间服务器为“其它”,可是“其它”域为空');
return;
} else {
loc += ntpServerOther4.value;
}
} else {
if( ntpServer4.selectedIndex > 0 )
loc += ntpServer4[ntpServer4.selectedIndex].value;
}
loc += '&ntpServer5=';
if( ntpServer5.selectedIndex == ntpServers.length+1 ) {
if( ntpServerOther5.value.length == 0 ) { // == Other
alert('第五时间服务器为“其它”,可是“其它”域为空');
return;
} else {
loc += ntpServerOther5.value;
}
} else {
if( ntpServer5.selectedIndex > 0 )
loc += ntpServer5[ntpServer5.selectedIndex].value;
}
loc += '&timezone_offset=' + cboTimeZone[cboTimeZone.selectedIndex].value;
loc += '&timezone=' + getTimeZoneName(cboTimeZone.selectedIndex);
loc += '&ntpWan=' + ntpWan.value;
loc += '&use_dst=0';
var ntpIntervalVal = parseInt(ntpInterval.value);
if(isNaN(ntpIntervalVal) || ntpIntervalVal < 3600 || ntpIntervalVal > 604800){
alert('同步间隔范围为3600-604800');
return;
}
loc += '&ntpInterval=' + ntpIntervalVal;
} else {
loc += '0';
}
}
loc += '&sessionKey=' + sessionKey;
var code = 'location="' + loc + '"';
eval(code);
}
函数先对是否开启自动更新时间进行判断,随后进入操作。
在我们设置完参数后,函数会把我们提交过来的参数经过几个步骤的参数拼接,然后用eval()函数进行提交操作,最后httpd调用sntp程序对参数进行操作。
我们可以看到,在一系列的操作中,没有任何的函数对我们提交的参数进行过滤和拦截,最后此漏洞的发生还是在过滤不严上面。
下面我们来看下华为和中兴对NTP漏洞的防范,他们的代码一样。。。
在我们提交ntpserver地址后,会对我们自定义的值进行检查和过滤,由isTValidName()函数进行,只有合规的参数才能进入后面操作
if(isTValidName(ntpServerOther1.value) == false)
{
AlertEx('第一级SNTP服务器的地址无效。');
return;
}
Form.addParameter('NTPServer1',ntpServerOther1.value)
我们来跟进 isTValidName()函数,可以看到,对大多数的特殊字符进行了检测,这些字符正是我们执行命令需要的,有了这个安全函数的检测,漏洞自然就不存在了。
function isTValidName(name) {
var i = 0;
var unsafeString = "\"<>%\\^[]`\+\$\,='#&:;*/{} \t";
for ( i = 0; i < name.length; i++ ) {
for( j = 0; j < unsafeString.length; j++)
if ( (name.charAt(i)) == unsafeString.charAt(j) )
return false;
}
return true;
}