close

作者:The_Itach1@知道創宇404實驗室日期:2022年11月22日


設備簡述


Tenda WiFi6 雙頻無線路由器工作在2.4GHz和5GHz頻段,支持802.11ax技術,雙頻並發無線速率高達2976Mbps;支持OFDMA技術,實現在同一時刻多個用戶同時並行傳輸,提高數據傳輸效率;支持寬帶賬號密碼遷移,替換舊路由時,忘記寬帶賬號密碼也不怕;支持IPv6,無需經過地址轉換(NAT),上網更暢快。

這個路由器只有一個指示燈

常亮, 路由器正在啟動或者已聯網成功

慢閃,路由器未聯網。

快閃3秒, 網口有設備接入或者有設備移除

快閃2分鐘 路由器正在進行WPS協商

然後就是路由器的幾個接口

POWER, 電源接口,連接包裝盒內的電源適配器。

WPS/RST,WPS、REST復用按鈕。作為WPS按鈕:按一下,即開始WPS協商,指示燈快閃(有效時間2分鐘)。路由器進入WPS協商狀態。2分鐘內,客戶端可以通過WPS-PBC方式便捷地連接到路由器的無限網絡,無需輸入無線密碼。作為RST按鈕:路由器正常運行時,按住此按鈕約8秒,當知識點快閃時鬆開,路由器將會恢復出廠設置。當指示燈變成常亮時,恢復出廠設置成功。

WAN,互聯網接口。10/100/1000Mbps自適應。用於鏈接光貓,DSL貓,有線電視貓,或寬帶網口。

1、2、IPTV/3 內網接口、IPTV口復用,默認為內網接口。10/100/1000Mbps自適應。路由器啟用IPTV功能後,默認綁定IPTV3/接口作為IPTV接口,只能連接機頂盒。根據需要可以修改IPTV口。

裡面的恢復出廠設置,我們可能會用到。





硬件分析


拆機

我們先拆機對其進行簡單的分析,拆機過程對我一個新手還是比較痛苦。

觀察路由器底部,只有兩個螺絲,用對應的螺絲刀將其拆除,接下來就是拆開上面的那層塑料外殼,非常堅硬,並且其除了接口那一邊,其他方向都是包裹着下方,縫隙很小,所以只能從接口那一方開始撬開,插解過程中,非常難撬開兩邊的部分,這裡我是使用了很多螺絲釘(有塑料可以用塑料),不斷安放在以撬開的口中,然後繼續向下撬開,最後終於拆開了外殼,發現其結構是內扣,所以拆解需要向外撬。

然後已經可以看到電路板了,但是在電路板的上面和下面還有兩塊金屬散熱板,用6個小螺絲相連。

我們這裡就只用插上面的散熱板,就可以看到電路板了,將整個電路板翻轉過來,然後用螺絲刀將6個螺絲插下,然後發現仍然不能打開,觀察後發現,散熱板和下面電路板的兩個金屬蓋之間還有一層膠,而且有兩處,這裡我是用細小的小刀,一點一點刮出來的,費了很多時間。

接下來就可以看到整個電路板了,簡單分析下電路板的一些東西。

藍色的應該是4個串口,並且已經標好了名稱。紫色的是flash芯片,裡面保存着固件,可根據上面的印字去找到芯片相關的信息。綠色的就是4個天線,兩個5G,2個2.4G。

然後中間的金屬蓋我就沒有繼續拆解了,沒用到這個,拆解起來也比較麻煩,需要用扁形螺絲刀從邊緣的縫隙去撬開。





Flash芯片


去找了些關於flash芯片的介紹。

所謂Flash,是內存(Memory)的一種,但兼有RAM和ROM 的優點,是一種可在系統(In-System)進行電擦寫,掉電後信息不丟失的存儲器,同時它的高集成度和低成本使它成為市場主流。Flash 芯片是由內部成千上萬個存儲單元組成的,每個單元存儲一個bit。具有低功耗、大容量、擦寫速度快、可整片或分扇區在系統編程(燒寫)、擦除等特點,並且可由內部嵌入的算法完成對芯片的操作,因而在各種嵌入式系統中得到了廣泛的應用。作為一種非易失性存儲器,Flash在系統中通常用於存放 程序 代碼、常量表以及一些在系統掉電後需要保存的用戶數據等。常用的Flash為8位或16位的數據寬度,編程電壓為單3.3V。主要的生產廠商為INTEL、ATMEL、AMD、HYUNDAI等。Flash 技術根據不同的應用場合也分為不同的發展方向,有擅長存儲代碼的NOR Flash和擅長存儲數據的NAND Flash。

然後如果芯片上的印字沒被擦除的話,我們完全可以通過芯片印字來獲取到關於flash芯片的一些有用信息,圖片來自iot-security.wiki

完全可以獲得廠商,flash類別,大小,編程電壓,封裝類型,溫度等信息。

下面是Tenda Ax12的flag芯片印字,可以看到芯片印字為winbond 25Q128JVSQ 2104

可以看出是華邦(winbond)的芯片,可到官網去查閱其技術文檔獲取更多的信息。W25Q128JV - Serial NOR Flash - 閃存 - 華邦電子 (winbond.com)

進入技術文檔查看其引腳配置,如果我們打算用flash芯片來提取固件,我們需要芯片的引腳信息,Tenda Ax12的引腳應該是這一款。

其中第一個引腳會有一個圓點的凹槽,對應技術文檔中的圖片,在我上面拍攝的flash芯片也有體現。

關於Tenda Ax12設備的flash芯片的一些信息我們就已經知道了,可以嘗試對flash芯片進行固件提取,但是我這裡沒有設備,編程器,芯片夾這些設備,而且有點小貴。後面可能會買個便宜的CHA341A編程器,看看能不能提出來吧。

但是基本的過程還是需要知道,從芯片來提取固件主要分為兩種,由於沒有實操,所以更多的可能是文字描述。

拆解芯片

不插解芯片,飛線法讀取





拆解芯片提取固件


一般來說有兩種插解芯片的方式,熱風槍吹,還有個就是焊錫。

熱風槍吹:熱風槍設置好合適的溫度後,在芯片周圍元件貼上錫箔紙進行保護,然後開吹,最後用鑷子小心提取出芯片。

焊錫:用電烙鐵加熱上錫,分別對芯片兩側進行上錫,然後,用鑷子夾出。

然後就是將取出的芯片根據引腳放到彈跳座中,然後就是將USB線將編程器連接至電腦並打開編程器軟件,軟件自動識別芯片,提取即可,RT809F編程器,RT809H編程器都是常見的編程器,價格大概500左右。

留個具體操作的鏈接,方便以後實操可以看,https://blog.csdn.net/Freedom_hzw/article/details/104216532 這種拆解芯片的方式,優點就是離線讀取,然後缺點就是必須將芯片拆下來,可能會損傷芯片,或者設備。





飛線法讀取


用芯片夾夾住引腳,然後另一端接到編程器上,引腳要一一對應,而且芯片夾也分種類,有像一個小夾子的,有些是帶勾尖的。

然後將USB線將編程器連接至電腦並打開編程器軟件,就可以識別芯片,進行固件提取了。這種方法優點就是不用將芯片拆下來,幾乎不損傷設備,但是缺點就是可能有過電保護。

後面買了一個CHA341A編程器,用芯片夾來提取了固件,下面展示下其中的操作。

用到的工具有,對應的編程器軟件和驅動可以到淘寶賣家上的鏈接去下載。

CHA341A編程器:需要注意引腳1的位置

SOP8免拆夾:紅色的線,就是引腳1

轉接板:上面有標註引腳

然後將其連接起來,可參考CH341A 編程器和SOIC8連接器免拆夾具組裝方法。

然後就是將芯片夾夾住芯片,然後usb接口接上電腦,感覺芯片夾不是太好用,那種鈎子可能跟方便一點。

安裝好驅動後,打開編程器軟件,點擊檢測後,就自動識別好了flash芯片的類型,點擊讀取開始讀取數據。

讀取完成後保存,然後binwalk解下固件包,即可獲取到文件系統。





串口調試(UART)


採用串口調試的方式也是可以提取固件的,如果運氣好,知道登錄密碼,或者廠商沒有防護,是可以獲取到設備shell的。

需要的工具有FT232,杜邦線,萬用表,SecureCRT軟件。

FT232 USB轉UART串口模塊。

UART引腳作用

VCC:供電pin,一般是3.3v-5v,正極

GND:接地,負極

RXD:接收數據引腳

TXD:發送數據引腳

雖然Tenda Ax12設備已經提供了引腳名稱,但是我們也可以使用萬用表來驗證一下。

定位GND

將萬用表扭至蜂鳴檔,將一隻表筆抵住電源焊錫點,另一個表筆抵住通孔位置進行測試,發出蜂鳴聲的通孔,就可以初步判定為GND。

定位VCC

將萬用表扭至直流20V上,將一隻表筆放置於GND上,另一隻表筆依次對其它通孔進行測試,查看哪個是電壓3.3V,如果是大概率就是VCC串口,雖然VCC串口我們可能用不到,但是這個我們可以排除這一個串口是其他串口的可能。

定位TXD

每次有數據傳輸的時候該引腳電壓都會發生變化。路由器開機的時候有啟動信息會從這個引腳輸出,這時候電壓就會發生變化,此引腳即為TXD。

定位RXD

其他3個引腳都以確認,剩下的一個就是RXD。

現在我們就知道了電路板上GND就是GND,IN就是RXD,OUT就是TXD,3V3就是VCC。但是連接杜邦線的時候需要這樣連接,一般來說連GND,RXD,TXD就可以了。

FT232上的TXD連接到電路板的RXD(IN)

FT232上的RXD連接到電路板的RXD(TXD)

FT232上的GND連接到電路板的GND

連接完成後如下。

然後打開SecureCRT,網上隨便都能找到下載和使用,配置好,就可以連接了。

然後重啟路由器,就可以看到在打印啟動日誌了。

由於日誌太多,窗口沒法完全顯示,所以可以設置下,將日誌導出,就可在指定目錄下查看啟動日誌了。

大概瀏覽的日誌內容後,可以獲得一些有用的信息,文件系統是squashfs,linux版本,架構,以及掛載情況。

20221102_10:11:42: 20221102_10:11:42: ROM VER: 2.1.020221102_10:11:42: CFG 0520221102_10:11:42: B20221102_10:11:44: 20221102_10:11:44: 20221102_10:11:44: U-Boot 2016.07-INTEL-v-3.1.177 (Nov 25 2020 - 09:48:15 +0000)20221102_10:11:44: 20221102_10:11:44: interAptiv20221102_10:11:44: cps cpu/ddr run in 800/666 Mhz20221102_10:11:44: DRAM: 224 MiB20221102_10:11:44: manuf ef, jedec 4018, ext_jedec 000020221102_10:11:44: SF: Detected W25Q128BV with page size 256 Bytes, erase size 64 KiB, total 16 MiB20221102_10:11:44: *** Warning - Tenda Environment, using default environment20221102_10:11:44: 20221102_10:11:44: env size:8187, crc:a1e4bcc2 need a1e4bcc220221102_10:11:44: In: serial20221102_10:11:44: Out: serial20221102_10:11:44: Err: serial20221102_10:11:44: Net: multi type20221102_10:11:44: Internal phy firmware version: 0x854820221102_10:11:44: GRX500-Switch20221102_10:11:44: 20221102_10:11:44: Type run flash_nfs to mount root filesystem over NFS20221102_10:11:44: 20221102_10:11:49: Hit ESC to stop autoboot: 0 20221102_10:11:49: Wait for upgrade... use GRX500-Switch20221102_10:11:55: tenda upgrade timeout.20221102_10:11:55: manuf ef, jedec 4018, ext_jedec 000020221102_10:11:55: SF: Detected W25Q128BV with page size 256 Bytes, erase size 64 KiB, total 16 MiB20221102_10:11:55: device 0 offset 0x100000, size 0x20000020221102_10:11:58: SF: 2097152 bytes @ 0x100000 Read: OK20221102_10:11:58: ## Booting kernel from Legacy Image at 80800000 ...20221102_10:11:58: Image Name: MIPS UGW Linux-4.9.20620221102_10:11:58: Created: 2021-08-23 9:11:35 UTC20221102_10:11:58: Image Type: MIPS Linux Kernel Image (lzma compressed)20221102_10:11:58: Data Size: 2080384 Bytes = 2 MiB20221102_10:11:58: Load Address: a002000020221102_10:11:58: Entry Point: a002000020221102_10:11:59: Verifying Checksum ... OK20221102_10:12:01: Uncompressing Kernel Image ... OK20221102_10:12:01: [ 0.000000] Linux version 4.9.206 (root@ubt1-virtual-machine) (gcc version 8.3.0 (OpenWrt GCC 8.3.0 v19.07.1_intel) ) #0 SMP Mon Aug 23 03:34:58 UTC 202120221102_10:12:01: [ 0.000000] SoC: GRX500 rev 1.220221102_10:12:01: [ 0.000000] CPU0 revision is: 0001a120 (MIPS interAptiv (multi))20221102_10:12:01: [ 0.000000] Enhanced Virtual Addressing (EVA 1GB) activated20221102_10:12:01: [ 0.000000] MIPS: machine is EASY350 ANYWAN (GRX350) Main model20221102_10:12:01: [ 0.000000] Coherence Manager IOCU detected20221102_10:12:01: [ 0.000000] Hardware DMA cache coherency disabled20221102_10:12:01: [ 0.000000] earlycon: lantiq0 at MMIO 0x16600000 (options '')20221102_10:12:01: [ 0.000000] bootconsole [lantiq0] enabled20221102_10:12:01: [ 0.000000] User-defined physical RAM map:20221102_10:12:01: [ 0.000000] memory: 08000000 @ 20000000 (usable)20221102_10:12:01: [ 0.000000] Determined physical RAM map:20221102_10:12:01: [ 0.000000] memory: 08000000 @ 20000000 (usable)20221102_10:12:01: [ 0.000000] memory: 00007fa4 @ 206d7450 (reserved)20221102_10:12:01: [ 0.000000] Initrd not found or empty - disabling initrd20221102_10:12:01: [ 0.000000] cma: Reserved 32 MiB at 0x25c0000020221102_10:12:01: [ 0.000000] SMPCMP: CPU0: cmp_smp_setup20221102_10:12:01: [ 0.000000] VPE topology {2,2} total 420221102_10:12:01: [ 0.000000] Detected 3 available secondary CPU(s)20221102_10:12:01: [ 0.000000] Primary instruction cache 32kB, VIPT, 4-way, linesize 32 bytes.20221102_10:12:01: [ 0.000000] Primary data cache 32kB, 4-way, PIPT, no aliases, linesize 32 bytes20221102_10:12:01: [ 0.000000] MIPS secondary cache 256kB, 8-way, linesize 32 bytes.20221102_10:12:01: [ 0.000000] Zone ranges:20221102_10:12:01: [ 0.000000] DMA [mem 0x0000000020000000-0x0000000027ffffff]20221102_10:12:01: [ 0.000000] Normal empty20221102_10:12:01: [ 0.000000] Movable zone start for each node20221102_10:12:01: [ 0.000000] Early memory node ranges20221102_10:12:01: [ 0.000000] node 0: [mem 0x0000000020000000-0x0000000027ffffff]20221102_10:12:01: [ 0.000000] Initmem setup node 0 [mem 0x0000000020000000-0x0000000027ffffff]20221102_10:12:01: [ 0.000000] percpu: Embedded 12 pages/cpu s17488 r8192 d23472 u4915220221102_10:12:01: [ 0.000000] Built 1 zonelists in Zone order, mobility grouping on. Total pages: 3248020221102_10:12:01: [ 0.000000] Kernel command line: earlycon=lantiq,0x16600000 nr_cpus=4 nocoherentio clk_ignore_unused root=/dev/mtdblock6 rw rootfstype=squashfs do_overlay console=ttyLTQ0,115200 ethaddr=D8:32:14:F8:24:08 panic=1 mtdparts=spi32766.1:512k(uboot),128k(ubootconfigA),128k(ubootconfigB),256k(calibration),2m(kernel),12m(rootfs),-(res) init=/etc/preinit active_bank= update_chk= maxcpus=4 pci=pcie_bus_perf ethwan= ubootver= mem=128M@512M 20221102_10:12:01: [ 0.000000] PID hash table entries: 512 (order: -1, 2048 bytes)20221102_10:12:01: [ 0.000000] Dentry cache hash table entries: 16384 (order: 4, 65536 bytes)20221102_10:12:01: [ 0.000000] Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)20221102_10:12:01: [ 0.000000] Writing ErrCtl register=0000000020221102_10:12:01: [ 0.000000] Readback ErrCtl register=0000000020221102_10:12:01: [ 0.000000] Memory: 87656K/131072K available (5089K kernel code, 296K rwdata, 1268K rodata, 1268K init, 961K bss, 10648K reserved, 32768K cma-reserved)20221102_10:12:01: [ 0.000000] SLUB: HWalign=32, Order=0-3, MinObjects=0, CPUs=4, Nodes=120221102_10:12:01: [ 0.000000] Hierarchical RCU implementation.20221102_10:12:01: [ 0.000000] NR_IRQS:52720221102_10:12:01: [ 0.000000] EIC is off20221102_10:12:01: [ 0.000000] VINT is on20221102_10:12:01: [ 0.000000] CPU Clock: 800000000Hz mips_hpt_frequency 400000000Hz20221102_10:12:01: [ 0.000000] clocksource: gptc: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 9556302233 ns20221102_10:12:01: [ 0.000010] sched_clock: 32 bits at 200MHz, resolution 5ns, wraps every 10737418237ns20221102_10:12:01: [ 0.008263] Calibrating delay loop... 531.66 BogoMIPS (lpj=2658304)20221102_10:12:01: [ 0.069297] pid_max: default: 32768 minimum: 30120221102_10:12:01: [ 0.074089] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes)20221102_10:12:01: [ 0.080513] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes)20221102_10:12:01: [ 0.089028] CCA is coherent, multi-core is fine20221102_10:12:01: [ 0.098030] [vmb_cpu_alloc]:[645] CPU vpet.cpu_status = 11············20221102_10:12:04: [ 2.630752] Creating 7 MTD partitions on "spi32766.1":20221102_10:12:04: [ 2.635944] 0x000000000000-0x000000080000 : "uboot"20221102_10:12:04: [ 2.641983] 0x000000080000-0x0000000a0000 : "ubootconfigA"20221102_10:12:04: [ 2.647466] 0x0000000a0000-0x0000000c0000 : "ubootconfigB"20221102_10:12:04: [ 2.652796] 0x0000000c0000-0x000000100000 : "calibration"20221102_10:12:04: [ 2.658323] 0x000000100000-0x000000300000 : "kernel"20221102_10:12:04: [ 2.663127] 0x000000300000-0x000000f00000 : "rootfs"20221102_10:12:04: [ 2.668196] mtd: device 6 (rootfs) set to be root filesystem20221102_10:12:04: [ 2.672755] 1 squashfs-split partitions found on MTD device rootfs20221102_10:12:04: [ 2.678831] 0x000000d00000-0x000001000000 : "rootfs_data"20221102_10:12:04: [ 2.685523] 0x000000f00000-0x000001000000 : "res"20221102_10:12:04: [ 2.689973] Lantiq SoC SPI controller rev 9 (TXFS 32, RXFS 32, DMA 1)20221102_10:12:04: [ 2.705499] libphy: Fixed MDIO Bus: probed20221102_10:12:04: [ 2.713792] libphy: gswitch_mdio: probed20221102_10:12:04: [ 2.719788] libphy: gswitch_mdio: probed20221102_10:12:04: [ 2.723371] lro_sram_membase_res0 from DT: a2013000 20221102_10:12:04: [ 2.727580] ltq_toe_membase: e2000000 and lro_sram_membase_res0: e201300020221102_10:12:04: [ 2.734666] TOE Init Done !!

然後等待一段時間,就會出現登錄了,前提是路由器要接網線(被坑了一段時間),否則不會出現登錄,猜測的原因應該是未接網線時,應該在某個地方被阻塞了,並且Tenda路由器的一些產品,都是支持Telnet連接的,但是Telnet的服務需要我們自己去打開,兩個登錄過程都是一樣的。





Shell登錄


無論是串口還是Telnet,都是需要密碼的,username為root,但是password不知道,但是通過搜索可以知道,對於Tenda路由器部分設備,開啟Telnet服務的訪問方式是http://192.168.0.1/goform/telnet,CVE-2018-5770,並且對於密碼,同樣存在爆破得到密碼其密碼為Fireitup,CVE-2020-10988。

並且在/etc/shadow文件中,我們也能看到使用了md5加密的痕跡,想了解md5(unix)加密可以看看這篇文章md5(unix)原理分析。我這裡還是想了解下具體代碼處理過程,login命令實際busybox程序處理的,於是將busybox拖到了ida中查看,發現處理過程的具體代碼在sub_45A378。

先是獲取用戶名,然後根據這個用戶名調用getpwnam函數去/etc/shadow尋找對應的名稱,返回spwd結構體。

struct spwd{ char *sp_namp; /* 登錄名 */ char *sp_pwdp; /* 加密密碼 */ long sp_lstchg; /* 上次更改日期 */ long sp_min; /* 更改密碼的最少天數 */ long sp_max; /* 更改密碼的最大天數*/ long sp_warn; /* 警告期 */ long sp_inact; /* 最長不活動天數 */ long sp_expire; /* 帳戶到期日期 */ unsigned long sp_flag; /* 未使用 */ };

這裡可以ida添加這個結構體,或許可以方便後面的分析,添加方式為View-->Open Subviews-->Local Type,然後右鍵Insert,將結構體複製進去,點擊ok即可,後面在對httpd文件分析的時候,也會用到Goahead裡面的一些結構體。

LABEL_34:

查看驗證函數,其中主要是打印Password: 字符串,並接受我們的輸入,然後提取/etc/shadow中的salt,然後調用crypt()函數,將其進行md5加密,最後和密文進行比較。

然後再貼一下hashcat的爆破過程吧,hashcat -m 500 -a 0 ./shadow.txt ./pwd.txt --force

Telnet的連接實際上也是調用的這個login,Telnet服務開啟會執行這樣的命令。

int __fastcall sub_41AE00(int a1){ system("/etc/init.d/telnet restart"); sub_415368(a1, "load Telnet success."); return sub_415B54(a1, 200);}

去到對應文件查看,發現一連串下來,最後還是調用的,/bin/login





固件提取


其實除了從芯片中提取固件,還可以直接到官網下載,或者在串口獲取shell後,使用一些命令,比如說nc,ftp,等命令將路由器的一些文件傳出到主機中,前提是路由器的shell需要支持這些命令。

這裡我採用最簡單的nc命令來進行提取固件,這裡我選擇Telnet連接,方便一些。

首先查看系統磁盤分區信息,proc/mtd文件保存着系統的磁盤分區信息,然後使用dd命令獲取Tenda Ax1的文件系統鏡像。

然後關閉主機防火牆,確保路由器shell和主機之間能ping通,接着用nc命令將tenda.bin到主機中。

nc -lp 1234 > tenda.bin

路由器shell連接上,進入tmp目錄,nc連接主機,發送文件。

cd /tmpnc 192.168.0.157 1234 < tenda.bin

效果如下

然後binwalk解包後的文件系統和官方的一致,如果想導出其他文件也可使用這種方式。





Goahead源碼分析


將固件解包後,就可以去看http服務對應的處理文件/usr/sbin/httpd,看了一會發現這好像是Goahead的架構,裡面的回調函數很多,所以理解架構可以幫助我們理解處理流程,以及方便打斷點進行調試。

搜索字符串可以發現2.1.8,所以應該是Goahead2.1.8的版本,在github上找到一個2.1.8源碼:https://github.com/trenta3/goahead-versions 官網文檔:https://www.embedthis.com/goahead/doc

一些全局變量

//main.cstatic char_t *rootWeb = T("web"); /* Root web directory */static char_t *password = T(""); /* Security password */static int port = 80; /* Server port */static int retries = 5; /* Server port retries */static int finished; /* Finished flag *///sock.csocket_t **socketList; /* List of open sockets */int socketMax; /* Maximum size of socket */int socketHighestFd = -1; /* Highest socket fd opened *///handler.cstatic websUrlHandlerType *websUrlHandler; /* URL handler list */static int websUrlHandlerMax; /* Number of entries */static int urlHandlerOpenCount = 0; /* count of apps */

rootWeb就是Web服務器的根目錄,在Tenda Ax12中,實際上就是/www。然後就是密碼password,端口port為80端口,嘗試次數retries為5,finished則是一個循環的標誌。

socketList是一個結構體數組,保存所有的socket,socketMax是當前所有socket的數量值。

websUrlHandler是一個指針數組,指向websUrlHandlerType這個結構體,這個結構體後面會分析。websUrlHandlerMax就是當前url handler的數量值。

main.c

/* * Main -- entry point from LINUX */int main(int argc, char** argv){/* * Initialize the memory allocator. Allow use of malloc and start * with a 60K heap. For each page request approx 8KB is allocated. * 60KB allows for several concurrent page requests. If more space * is required, malloc will be used for the overflow. */ bopen(NULL, (60 * 1024), B_USE_MALLOC); signal(SIGPIPE, SIG_IGN);/* * Initialize the web server */ if (initWebs() < 0) { return -1; }#ifdef WEBS_SSL_SUPPORT websSSLOpen();#endif/* * Basic event loop. SocketReady returns true when a socket is ready for * service. SocketSelect will block until an event occurs. SocketProcess * will actually do the servicing. */ while (!finished) { if (socketReady(-1) || socketSelect(-1, 1000)) { socketProcess(-1); } websCgiCleanup(); emfSchedProcess(); }#ifdef WEBS_SSL_SUPPORT websSSLClose();#endif#ifdef USER_MANAGEMENT_SUPPORT umClose();#endif/* * Close the socket module, report memory leaks and close the memory allocator */ websCloseServer(); socketClose();#ifdef B_STATS memLeaks();#endif bclose(); return 0;}

可以看到先是bopen()分配了內存,然後調用了initWebs()去初始化web服務,這個函數也是後面要重點分析的函數。然後就是while循環,裡面有下面幾個函數

socketReady(),就是判斷是否存在準備處理事件的套接字,有則會返回TRUE,其實現方式是遍歷socketList,獲取socket_t *sp的結構體成員信息進行if判斷。

socketSelect(),此調用使用由 socketRegisterInterest 定義的感興趣事件的掩碼。它阻塞調用者,直到發生合適的 I/O 事件或超時。

socketProcess(),處理掛起的套接字 I/O 事件。

websCgiCleanup(),需要檢查 cgiList 中的任何條目是否已完成,如果已完成,則處理其輸出並清理。

emfSchedProcess(),以循環方式將任務從隊列中取出。

後面部分就是關閉WebServer,關閉套接字,釋放內存,分別由websCloseServer(),socketClose(),bclose()實現。

initWebs()

static int initWebs(){ struct hostent *hp; struct in_addr intaddr; char host[128], dir[128], webdir[128]; char *cp; char_t wbuf[128];/* * Initialize the socket subsystem */ socketOpen();#ifdef USER_MANAGEMENT_SUPPORT/* * Initialize the User Management database */ umOpen(); umRestore(T("umconfig.txt"));#endif/* * Define the local Ip address, host name, default home page and the * root web directory. */ if (gethostname(host, sizeof(host)) < 0) { error(E_L, E_LOG, T("Can't get hostname")); return -1; } if ((hp = gethostbyname(host)) == NULL) { error(E_L, E_LOG, T("Can't get host address")); return -1; } memcpy((char *) &intaddr, (char *) hp->h_addr_list[0], (size_t) hp->h_length);/* * Set ../web as the root web. Modify this to suit your needs */ getcwd(dir, sizeof(dir)); if ((cp = strrchr(dir, '/'))) { *cp = '\0'; } sprintf(webdir, "%s/%s", dir, rootWeb);/* * Configure the web server options before opening the web server */ websSetDefaultDir(webdir); cp = inet_ntoa(intaddr); ascToUni(wbuf, cp, min(strlen(cp) + 1, sizeof(wbuf))); websSetIpaddr(wbuf); ascToUni(wbuf, host, min(strlen(host) + 1, sizeof(wbuf))); websSetHost(wbuf);/* * Configure the web server options before opening the web server */ websSetDefaultPage(T("default.asp")); websSetPassword(password);/* * Open the web server on the given port. If that port is taken, try * the next sequential port for up to "retries" attempts. */ websOpenServer(port, retries);/* * First create the URL handlers. Note: handlers are called in sorted order * with the longest path handler examined first. Here we define the security * handler, forms handler and the default web page handler. */ websUrlHandlerDefine(T(""), NULL, 0, websSecurityHandler, WEBS_HANDLER_FIRST); websUrlHandlerDefine(T("/goform"), NULL, 0, websFormHandler, 0); websUrlHandlerDefine(T("/cgi-bin"), NULL, 0, websCgiHandler, 0); websUrlHandlerDefine(T(""), NULL, 0, websDefaultHandler, WEBS_HANDLER_LAST); /* * Now define two test procedures. Replace these with your application * relevant ASP script procedures and form functions. */ websAspDefine(T("aspTest"), aspTest); websFormDefine(T("formTest"), formTest);/* * Create the Form handlers for the User Management pages */#ifdef USER_MANAGEMENT_SUPPORT formDefineUserMgmt();#endif/* * Create a handler for the default home page */ websUrlHandlerDefine(T("/"), NULL, 0, websHomePageHandler, 0); return 0;}

先是調用socketOpen(),初始化socket系統,就是對sock.c的一些全局變量進行初始化。然後對IP,host name,還有網頁的根目錄進行獲取和賦值。調用websSetDefaultDir()設置根目錄,websSetIpaddr()設置ip地址,websSetHost()設置host name。websSetDefaultPage()設置默認訪問頁,websSetPassword()設置密碼。websOpenServer(port, retries),在指定端口打開webserver 如果這個端口不可用,就延續下一個,retries就是失敗後可嘗試的次數,裡面實現了對websUrlHandler,websUrlHandlerMax的初始化,都為0。

下面要分析的就是websUrlHandlerDefine和一些結構體了,這是搞懂Gohead是如何如何處理前端發過來的請求的關鍵地方。

先來看下websUrlHandlerType結構體

typedef struct { int (*handler)(webs_t wp, char_t *urlPrefix, char_t *webDir, int arg, char_t *url, char_t *path, char_t *query); /* Callback URL handler function */ char_t *webDir; /* Web directory if required */ char_t *urlPrefix; /* URL leading prefix */ int len; /* Length of urlPrefix for speed */ int arg; /* Argument to provide to handler */ int flags; /* Flags */} websUrlHandlerType;

可以看到,其包含了以下成員

函數指針handler,是這個Url的回調處理函數。

然後就是webDir,Web 目錄的可選根目錄路徑,但是給websUrlHandlerDefine函數傳參是,一般都為0。

urlPrefix,要匹配的 URL 前綴,比如說「/goform」,"/cgi-bin",「/」,「」等。

len,urlPrefix字符串的長度,後面排序會用到。

arg,傳遞給處理函數的參數。

flags,定義匹配順序,有兩個WEBS_HANDLER_LAST、WEBS_HANDLER_FIRST,那些先進行處理,那些後進行處理。

再來看看webs_t結構體

/* * Per socket connection webs structure */typedef struct websRec { ringq_t header; /* Header dynamic string */ time_t since; /* Parsed if-modified-since time */ sym_fd_t cgiVars; /* CGI standard variables */ sym_fd_t cgiQuery; /* CGI decoded query string */ time_t timestamp; /* Last transaction with browser */ int timeout; /* Timeout handle */ char_t ipaddr[32]; /* Connecting ipaddress */ char_t type[64]; /* Mime type */ char_t *dir; /* Directory containing the page */ char_t *path; /* Path name without query */ char_t *url; /* Full request url */ char_t *host; /* Requested host */ char_t *lpath; /* Cache local path name */ char_t *query; /* Request query */ char_t *decodedQuery; /* Decoded request query */ char_t *authType; /* Authorization type (Basic/DAA) */ char_t *password; /* Authorization password */ char_t *userName; /* Authorization username */ char_t *cookie; /* Cookie string */ char_t *userAgent; /* User agent (browser) */ char_t *protocol; /* Protocol (normally HTTP) */ char_t *protoVersion; /* Protocol version */ int sid; /* Socket id (handler) */ int listenSid; /* Listen Socket id */ int port; /* Request port number */ int state; /* Current state */ int flags; /* Current flags -- see above */ int code; /* Request result code */ int clen; /* Content length */ int wid; /* Index into webs */ char_t *cgiStdin; /* filename for CGI stdin */ int docfd; /* Document file descriptor */ int numbytes; /* Bytes to transfer to browser */ int written; /* Bytes actually transferred */ void (*writeSocket)(struct websRec *wp);#ifdef DIGEST_ACCESS_SUPPORT char_t *realm; /* usually the same as "host" from websRec */ char_t *nonce; /* opaque-to-client string sent by server */ char_t *digest; /* digest form of user password */ char_t *uri; /* URI found in DAA header */ char_t *opaque; /* opaque value passed from server */ char_t *nc; /* nonce count */ char_t *cnonce; /* check nonce */ char_t *qop; /* quality operator */#endif#ifdef WEBS_SSL_SUPPORT websSSL_t *wsp; /* SSL data structure */#endif} websRec;typedef websRec *webs_t;typedef websRec websType;

這個就是每個套接字連接網絡的結構體,包含了很多信息,就像我們bp抓包裡面包含的那些信息。

還有個就是socket_t

typedef struct { char host[64]; /* Host name */ ringq_t inBuf; /* Input ring queue */ ringq_t outBuf; /* Output ring queue */ ringq_t lineBuf; /* Line ring queue */ socketAccept_t accept; /* Accept handler */ socketHandler_t handler; /* User I/O handler */ int handler_data; /* User handler data */ int handlerMask; /* Handler events of interest */ int sid; /* Index into socket[] */ int port; /* Port to listen on */ int flags; /* Current state flags */ int sock; /* Actual socket handle */ int fileHandle; /* ID of the file handler */ int interestEvents; /* Mask of events to watch for */ int currentEvents; /* Mask of ready events (FD_xx) */ int selectEvents; /* Events being selected */ int saveMask; /* saved Mask for socketFlush */ int error; /* Last error */} socket_t;

這是socket套接字的結構體。

接下來分析websUrlHandlerDefine函數,這個函數是用來註冊各個URL具體的處理函數的。

/******************************************************************************//* * Define a new URL handler. urlPrefix is the URL prefix to match. webDir is * an optional root directory path for a web directory. arg is an optional * arg to pass to the URL handler. flags defines the matching order. Valid * flags include WEBS_HANDLER_LAST, WEBS_HANDLER_FIRST. If multiple users * specify last or first, their order is defined alphabetically by the * urlPrefix. */int websUrlHandlerDefine(char_t *urlPrefix, char_t *webDir, int arg, int (*handler)(webs_t wp, char_t *urlPrefix, char_t *webdir, int arg, char_t *url, char_t *path, char_t *query), int flags){ websUrlHandlerType *sp; int len; a_assert(urlPrefix); a_assert(handler);/* * Grow the URL handler array to create a new slot */ len = (websUrlHandlerMax + 1) * sizeof(websUrlHandlerType); if ((websUrlHandler = brealloc(B_L, websUrlHandler, len)) == NULL) { return -1; } sp = &websUrlHandler[websUrlHandlerMax++]; memset(sp, 0, sizeof(websUrlHandlerType)); sp->urlPrefix = bstrdup(B_L, urlPrefix); sp->len = gstrlen(sp->urlPrefix); if (webDir) { sp->webDir = bstrdup(B_L, webDir); } else { sp->webDir = bstrdup(B_L, T("")); } sp->handler = handler; sp->arg = arg; sp->flags = flags;/* * Sort in decreasing URL length order observing the flags for first and last */ qsort(websUrlHandler, websUrlHandlerMax, sizeof(websUrlHandlerType), websUrlHandlerSort); return 0;}

先定義了結構體指針sp,然後計算了長度len,分配內存空間,根據websUrlHandlerMax計算偏移,然後給sp結構體賦值,最後就是對所有的url handler進行個排序。
回到initWebs,分別有這幾種,安全處理程序,表單處理程序和默認網頁處理程序,默認主頁創建處理程序
websUrlHandlerDefine(T(""), NULL, 0, websSecurityHandler, WEBS_HANDLER_FIRST);websUrlHandlerDefine(T("/goform"), NULL, 0, websFormHandler, 0);websUrlHandlerDefine(T("/cgi-bin"), NULL, 0, websCgiHandler, 0);websUrlHandlerDefine(T(""), NULL, 0, websDefaultHandler, WEBS_HANDLER_LAST);websUrlHandlerDefine(T("/"), NULL, 0, websHomePageHandler, 0);

接下來我們要找到其是如何調用的這些handler,我們對全局變量websUrlHandler進行搜索,最後找到其在handler.c的websUrlHandlerRequest(webs_t wp)函數中找到了其調用方式。

websUrlHandlerRequest(webs_t wp)

/******************************************************************************//* * See if any valid handlers are defined for this request. If so, call them * and continue calling valid handlers until one accepts the request. * Return true if a handler was invoked, else return FALSE. */int websUrlHandlerRequest(webs_t wp){ websUrlHandlerType *sp; int i, first; a_assert(websValid(wp));/* * Delete the socket handler as we don't want to start reading any * data on the connection as it may be for the next pipelined HTTP/1.1 * request if using Keep Alive */ socketDeleteHandler(wp->sid); wp->state = WEBS_PROCESSING; websStats.handlerHits++; websSetRequestPath(wp, websGetDefaultDir(), NULL);/* * Eliminate security hole */ websCondenseMultipleChars(wp->path, '/'); websCondenseMultipleChars(wp->url, '/');/* * We loop over each handler in order till one accepts the request. * The security handler will handle the request if access is NOT allowed. */ first = 1; for (i = 0; i < websUrlHandlerMax; i++) { sp = &websUrlHandler[i]; if (sp->handler && gstrncmp(sp->urlPrefix, wp->path, sp->len) == 0) { if (first) { websSetEnv(wp); first = 0; } if ((*sp->handler)(wp, sp->urlPrefix, sp->webDir, sp->arg, wp->url, wp->path, wp->query)) { return 1; } if (!websValid(wp)) { trace(0, T("webs: handler %s called websDone, but didn't return 1\n"), sp->urlPrefix); return 1; } } }/* * If no handler processed the request, then return an error. Note: It is * the handlers responsibility to call websDone */ if (i >= websUrlHandlerMax) { /* * 13 Mar 03 BgP * preventing a cross-site scripting exploit websError(wp, 200, T("No handler for this URL %s"), wp->url); */ websError(wp, 200, T("No handler for this URL")); } return 0;}#ifdef OBSOLETE_CODE

所有的請求都會到這個函數來尋找其對應的有效處理程序,具體過程如下

調用socketDeleteHandler (wp->sid),刪除通過 socketCreateHandler 創建的套接字處理程序。

然後處理一些路徑安全的問題。

接着for循環遍歷websUrlHandler,根據sp->urlPrefix字符串,來決定對應的handler處理函數。

到這裡就又產生了個問題,websUrlHandlerRequest是在哪調用的呢,不斷向上跟會有這樣一個調用鏈。

aplwebsSocketEvent() |--判斷讀寫操作 |--讀websReadEvent() | |--websUrlHandlerRequest() | |--查找wbsUrlHandler數組,調用和urlPrefix對應的回調函數(websFormHandler(),websDefaultHandler()等) | |--寫,調用(wp->writeSocket)回調函數

接着我們需要知道websSocketEvent是哪來的,搜索發現,其是socketCreateHandler 創建的套接字處理程序。

aplwebsOpenServer() |--websOpenListen() |--調用socketOpenConnection(NULL, port, websAccept, 0),可是socketOpenConnection我在官方文檔中並沒有找到解釋。 |--websAccept() |--做一些檢查 |--socketCreateHandler(sid, SOCKET_READABLE, websSocketEvent, (int) wp) | |--把sid註冊為讀事件,初始化socket_t sp->handler = websSocketEvent等, 更新對應的socketList數組(handlerMask值等)

可以看出,是對socket_t sp->handler進行了賦值,所以其實最開始的地方就是在main函數中的while循環中,執行socketProcess(),從而調用socket_t sp->handler的處理函數進行相應的處理,下面是main函數中while循環的調用鏈。

|--(main loop)| |--socketReady(-1) || socketSelect(-1, 1000)| | |--輪詢socketList |--輪詢socketList中的handlerMask| | |--中的幾個變量 |--改變socketList中的currentEvents| || |--socketProcess()| |--輪詢socketList[]| |--socketReady()| |--socketDoEvent()| |--如果有新的連接(來自listenfd)就調用socketAccept()| | |--調用socketAlloc()初始化socket_t結構| | |--把socket_t結構加入 socketList數組| | |--調用socket_t sp->accept()回調函數| || |--如果不是新的連接就查找socketList數組調用socket_t sp->handler()回調函數

現在我們知道了這些url handler是如何被調用的了,但是還有個問題需要解決,就是websFormHandler表單處理程序,也就是當我們傳入表單,發送post請求時的handler,在Goahead中,是這樣定義ASP 腳本程序和表單功能的。

/* * Now define two test procedures. Replace these with your application * relevant ASP script procedures and form functions. */ websAspDefine(T("aspTest"), aspTest); websFormDefine(T("formTest"), formTest);/* * Define an ASP Ejscript function. Bind an ASP name to a C procedure. */int websAspDefine(char_t *name, int (*fn)(int ejid, webs_t wp, int argc, char_t **argv)){ return ejSetGlobalFunctionDirect(websAspFunctions, name, (int (*)(int, void*, int, char_t**)) fn);}/* * Define a form function in the "form" map space. */int websFormDefine(char_t *name, void (*fn)(webs_t wp, char_t *path, char_t *query)){ a_assert(name && *name); a_assert(fn); if (fn == NULL) { return -1; } symEnter(formSymtab, name, valueInteger((int) fn), (int) NULL); return 0;}static sym_fd_t formSymtab = -1; /* Symbol table for form handlers *//* * The symbol table record for each symbol entry */typedef struct sym_t { struct sym_t *forw; /* Pointer to next hash list */ value_t name; /* Name of symbol */ value_t content; /* Value of symbol */ int arg; /* Parameter value */} sym_t;typedef int sym_fd_t; /* Returned by symOpen */typedef struct { union { char flag; char byteint; short shortint; char percent; long integer; //注意這個,根據後面分析,這個代表了form表單的函數地址 long hex; long octal; long big[2];#ifdef FLOATING_POINT_SUPPORT double floating;#endif /* FLOATING_POINT_SUPPORT */ char_t *string; char *bytes; char_t *errmsg; void *symbol; } value; vtype_t type; unsigned int valid : 8; unsigned int allocated : 8; /* String was balloced */} value_t;

在Tenda Ax12中,更多使用的是websFormDefine,通過上面的代碼,我們可以知道下面的信息。

symEnter(formSymtab, name, valueInteger((int) fn), (int) NULL);,雖然找不到symEnter的定義,但是可以分析出來,這個函數應該是不斷向鍊表插入定義的form表單處理程序,主要包含name和具體的函數地址。

formSymtab全局變量應該是指向sym_t結構體鍊表的表頭。

接下來分析websFormHandler()

/* * Process a form request. Returns 1 always to indicate it handled the URL */int websFormHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, int arg, char_t *url, char_t *path, char_t *query){ sym_t *sp; char_t formBuf[FNAMESIZE]; char_t *cp, *formName; int (*fn)(void *sock, char_t *path, char_t *args); a_assert(websValid(wp)); a_assert(url && *url); a_assert(path && *path == '/'); websStats.formHits++;/* * Extract the form name */ gstrncpy(formBuf, path, TSZ(formBuf)); if ((formName = gstrchr(&formBuf[1], '/')) == NULL) { websError(wp, 200, T("Missing form name")); return 1; } formName++; if ((cp = gstrchr(formName, '/')) != NULL) { *cp = '\0'; }/* * Lookup the C form function first and then try tcl (no javascript support * yet). */ sp = symLookup(formSymtab, formName); if (sp == NULL) { websError(wp, 200, T("Form %s is not defined"), formName); } else { fn = (int (*)(void *, char_t *, char_t *)) sp->content.value.integer; a_assert(fn); if (fn) {/* * For good practice, forms must call websDone() */ (*fn)((void*) wp, formName, query);/* * Remove the test to force websDone, since this prevents * the server "push" from a form> */#if 0 /* push */ if (websValid(wp)) { websError(wp, 200, T("Form didn't call websDone")); }#endif /* push */ } } return 1;}

整個過程如下

|--websFormHandler()| |--strncpy(),strchr() 獲取formName| |--symLookup(formSymtab, formName) 遍歷鍊表,根據name返回對應的結構體。| |--sp->content.value.integer 從結構體中獲取到函數地址。| |--(*fn)((void*) wp, formName, query); 執行函數

到這裡,關於Goahead源碼的分析就差不多了,上面分析的內容可以幫助我們更好的去分析Tenda AX12的httpd程序,比如如何找到開發者自定義的處理函數,還有整個數據的處理流程,以及ida偽代碼符號表,結構體的恢復。





httpd漏洞挖掘


雖然網上關於Tenda路由器設備的cve很多,大部分是堆棧溢出,少部分命令注入,但是實際上這些cve在真實環境下是沒法利用的,基本上全都需要身份驗證,因為Tenda路由器的安全處理函數處理得很好,在我分析完整個登錄過程以及cookie驗證過程,都沒找到繞過的方式,所以還是只能搞一些經過身份驗證的漏洞,然後還是收穫了一些漏洞,分析過程中,我也使用了之前自己搞的一個idapython腳本插件Audit,可以幫助快速定位到一些函數,節省了一些時間。

以下的漏洞攻擊,都需要在有一次身份驗證下,也就是有一次可用的cookie,才能進行攻擊。





啟動部分分析


來到main函數,大部分的過程和Goahead的源碼差不多,但是有些區別的是host和username和userpass的獲取方式。

可以看到是調用GetValue這個函數,但究竟是從哪獲取到的呢,獲取到這個信息可能可以幫助我們找到userpass的存儲位置,這裡我沒找到關於GetValue這個函數的具體實現資料,但是我找到了關於OpenWrt系統UCI詳解的資料。

這些類似於network.lan.proto的字符串實際上都是uci的配置格式,其會保存在某個具體的配置文件中,一般都在etc目錄下,GetValue這個函數的內部,推斷應該就是使用了的內置函數來對值進行獲取。

然後我先是先對binwalk分離的固件進行了grep匹配特徵字符串,發現並沒有找到對應的配置文件,最後感覺還是得去真實設備中去匹配,Telnet連接上後進行匹配,成功找到username和userpass在/etc/config/admin文件中,proto,ipaddr在/etc/config/network中。





web登錄後台驗證過程分析


分析了Goahead的源碼後,我們知道了這種框架的數據處理過程,以及一些結構體,我們可以恢復這些結構體,以及去官方找一些mips架構的老固件來進行一些符號表的修復,讓分析過程變得簡單一些。





調試環境搭建


ida反編譯出來的代碼還是比較多,所以肯定需要進行調試分析,先搭建調試環境。首先去根據路由器架構下載對應編譯好的gdbserver,這裡我是下載的gdbserver-7.12-mips-mips32rel2-v1-sysv。

接下來就是和前面傳固件的方式,用nc命令來傳文件到路由器的linux系統中,只不過有點不一樣的是這次是從主機傳文件到路由器。

同樣也是關閉windows的防火牆,確保主機和路由器能ping通,主機監聽一個端口,並傳入文件。

nc -lp 1234 < gdbserver-7.12-mips-mips32rel2-v1-sysv

路由器shell連接上,進入tmp目錄,nc連接主機,接收文件。

cd /tmpnc 192.168.0.157 1234 > gdbserver

然後給gdbserver文件提供可執行權限。

chmod 777 ./gdbserver

效果如下。

然後gdbserver開啟監聽端口附加調試即可。

成功後,會出現Remote debugging from host ip。





前端分析


首先先對前端登錄的發包過程進行分析,隨便輸入一個密碼試一下,然後抓個包。

可以看到,訪問了/login/Auth這個接口,username默認為admin,password為md5(input),並且處理這個過程的文件應該是login.js。

可在瀏覽器中調試一下,大概分析下流程就是,註冊了一個登陸過程的回調函數。

var serviceUrl = '/login/Auth',authService = new PageService(serviceUrl),loginPageView = new PageView(),loginPageLogin = new PageLogic(loginPageView, authService);loginPageLogin.init();

然後每當登陸鍵按下,則會觸發處理函數。

view.addSubmitHandler(function () { that.validate.checkAll(); });this.addSubmitHandler = function (callBack) { $('#subBtn').on('click', function (e) { e.preventDefault(); callBack.apply(); });

接下來就是,獲取username和password,檢測是否有效,然後將password進行md5加密,然後發送到後端。

this.validate = $.validate({ custom: function () { var username = view.getUsername(), password = view.getPassword(); function checksValid(username, password) { return username !== '' && password !== ''; } if (!checksValid(username, password)) { return _("Please specify a login password."); } }, success: function () { var data = view.getSubmitData(); authService.login(data, view.showSuccessful, view.showError); }, error: function (msg) { view.showInvalidError(msg); }}); //md5加密password,返回表單 this.getSubmitData = function () { var ret = ''; ret = { username: this.getUsername(), password: hex_md5(this.getPassword()) }; return ret; }; //調用login函數,以POST的方式發送到後端進行驗證 this.login = function (subData, successCallback, errorCallback) { $.ajax({ url: url, type: "POST", data: subData, success: successCallback, error: errorCallback }); };

然後會根據後端傳的值來決定,是否顯示登陸錯誤,如果傳的值為1,就代表密碼錯誤。
this.showSuccessful = function (str) { var num = str; if (num == 1) { $('#login-message').html(_("Incorrect password.")); } else { // window.location.href = "/main.html"; //解決android系統下360瀏覽器不能正常登陸問題 window.location.reload(true); }

這裡我們就明白了登陸過程中前端是如何向後端發送數據的了。





分析R7WebsSecurityHandler()


這個函數是路由器的安全處理函數,無論訪問什麼url,都會經過這個函數,登錄後台的驗證,以及訪問各種接口時的cookie驗證都在這個函數進行處理。

我們直接先訪問http://ip,但是實際上的url會是http://ip/,其會先經過R7WebsSecurityHandler(),遍歷一些接口,發現都不是,然後就會return 0,下面這些資源,都是可直接訪問的,不需要驗證。

v12 = strncmp(a5, "/public/", 8); v13 = 4521984; if ( !v12 ) goto LABEL_24; v14 = strncmp(a5, "/lang/", 6); v13 = 4521984; if ( !v14 ) goto LABEL_24; if ( strstr(a5, "img/main-logo.png") ) goto LABEL_24; if ( strstr(a5, "reasy-ui-1.0.3.js") ) goto LABEL_24; if ( !strncmp(a5, "/favicon.ico", 12) ) goto LABEL_24; v13 = 4521984; if ( !*(_DWORD *)&wp->type[22] ) goto LABEL_24; v15 = strncmp(a5, "/kns-query", 10); v13 = 4521984; if ( !v15 ) goto LABEL_24; if ( !strncmp(a5, "/wdinfo.php", 11) ) goto LABEL_24; v16 = strlen((int)a5); v13 = 4521984; if ( v16 == 1 && *a5 == 47 ) goto LABEL_24; v17 = strncmp(a5, "/redirect.html", 14); v13 = 4521984; if ( !v17 || !strncmp(a5, "/goform/getRebootStatus", 23) ) {LABEL_24: puts("------ don't need check user -------", v13); return 0; } if ( dword_4697F8 && !memcmp(v56, "/login.html", 10) ) { dword_4697F8 = 0; return 0; } if ( i == 4 && !strncmp(a5, "/loginerr.html", 14) ) return 0; if ( (unsigned int)strlen((int)v56) >= 4 ) { v19 = strchr(v56, 46); if ( v19 ) { v20 = v19 + 1; if ( !memcmp(v19 + 1, "gif", 3) || !memcmp(v20, "png", 3) || !memcmp(v20, "js", 2) || !memcmp(v20, "css", 3) || !memcmp(v20, "jpg", 3) || !memcmp(v20, "jpeg", 3) ) { memset(v58, 0, 128); snprintf(v58, 128, "/www%s", v56); if ( !access(v58, 0) ) return 0; } } }

發現沒有處理'/'的條件後,會交給websHomePageHandler來處理,其會根據程序初始化中定義好的websDefaultPage,也就是main.html,然後重定向到main.html。

緊接着,又會經過R7WebsSecurityHandler(),然後經過遍歷,最後會到到達這個位置LABEL_149,又將會重定向到login.html,login.html是不需要身份驗證的,繼續調試下去就會出現登錄界面。

接下來我們將斷點打在,190行的/login/Auth,這就對應了前端的那個接口,我們隨便輸入密碼進行測試。

斷點斷下來後,分析過程,先獲取username和password,然後和保存在全局變量中的用戶名和密碼進行比較。

這裡先看密碼不正確的處理流程,這裡會到達LABEL_86,調用websWrite向WebRec結構體寫了些東西,然後調用了webDone()函數,結束這次請求,websWrite((int)wp, "%s", "1");,我猜測這應該是傳給前面login.js的str,使得num=1,從而顯示密碼錯誤。

接着再來看正確密碼的處理流程,經過一些if判斷後,對loginUserInfo進行賦值,也就是訪問者的ip地址,最後會跳轉到LABEL_118。

LABEL_118,這個地方就是在生成cookie,然後發送到前端了,並且其cookie的組成是有一種固定的方式的。

為了搞明白cookie的生成過程,進入websRedirectOpCookie分析。

所以實際上cookie就是3部分組成password+3個a到z的隨機數+3個字符(由訪問者的ip地址決定是哪三個)。

接下來就是再次訪問main.html,但和之前不同的是,這次loginUserInfo有值了,其會導致i!=4,執行這樣的流程,其實就是在驗證cookie是否有效。

驗證cookie是否有效,然後跳轉到對應的界面,訪問其他接口也一樣,都需要驗證cookie的有效性。

登錄和cookie驗證的分析就差不多了,其實還有很多地方可以繼續分析,比如說我這個瀏覽器登錄了,換個瀏覽器會怎麼樣,或者換個ip訪問會怎麼樣,根據其cookie的生成過程來說,同一個ip訪問時,最多只有一個有效cookie,換一個瀏覽器登錄,就會造成之前瀏覽器的cookie失效,從而需要重新登錄。





CSRF恢復出廠設置


大部分Tenda的設備的CSRF漏洞都是一些接口直接提供system命令,這裡我也找到一個Tenda Ax12下還未提交過的,也就是/goform/SysToolRestoreSet。

攻擊也很簡單,在經過身份驗證後 Get訪問這個接口,即可讓整個路由器恢復出廠設置。





CSRF刪除修改密碼,修改WiFi名稱


這個漏洞發生在/goform/fast_setting_wifi_set接口下,這個接口實際上是設備恢復出廠設置後,重新設置WiFi名稱,和密碼的接口。我們可以控制傳入的web參數,來達到修改WiFi名稱,修改密碼的效果,並且在這個函數中,由於是重新設置的函數,所以並不會和先前的密碼進行對比。

正常情況下,我們在恢復出廠設置後,其會讓我們輸入WiFi名稱,無線密碼,以及5-32位的管理員密碼,抓包如下。

可以看到一些參數,如果選擇無須密碼,對應參數的值就是空。

ssid代表WiFi名稱

WrlPassword代表連WiFi的密碼

loginPwd代表管理員密碼,被md5加密了。

然後在來分析這個接口對應的處理函數sub_4335C0。

先是獲取ssid,判斷是否為空,然後如果WiFi密碼不為空,默認以psk-mixed方式加密,然後將WiFi名,加密方式,密碼給到v17,然後調用tapi_set_wificfg,猜測應該是在設置WiFi密碼吧,然後以同樣的方式設置5g。

然後就是設置管理員密碼了,先獲取前端傳入的參數loginPwd,然後設置到/etc/config/admin文件中,下面是一些無關緊要的if判斷,感覺沒什麼作用,而且管理員密碼已經寫入到了admin文件中。

後序就是timeZone的設置,一些webWrite,以及一些重新啟動的過程。

所以如果在有一次有效cookie的前提下,我們完全可以構造參數,來達到修改WiFi名稱密碼,以及管理員密碼的目的。

POST /goform/fast_setting_wifi_set HTTP/1.1Host: 192.168.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0Accept: */*Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencoded; charset=UTF-8X-Requested-With: XMLHttpRequestContent-Length: 10Origin:http://192.168.0.1Connection: closeCookie: password=xxxxxxxxxxReferer:http://192.168.0.1/main.htmlssid=xxx&WrlPassword=xxx&power=high&loginPwd=(md5(xxx))





未成功的XSS


Tenda Ax12設備的WiFi名稱和接入的設備名稱都是可以設置的,這可能會導致xss,我對WiFi名稱和設備名稱都進行了測試,遺憾的是未能觸發攻擊,但是我感到很疑惑,因為名字都顯示出來了,卡了好久最後才找到原因。

這裡以設備名稱為例,首先我們需要繞過前端長度驗證,抓包修修改即可。

然後查看顯示也是正常,html也正常,就有點奇怪為什麼沒彈窗。

卡了一會,最後將整個html下載下來才發現,其對<>字符進行了轉義。

<div class="dev-name text-fixed" style="padding-right: 30px;" title="&lt;script&gt;alert(&quot;The_Itach1&quot;)&lt;/script&gt;"> <span class="dev-name-text">&lt;script&gt;alert("The_Itach1")&lt;/script&gt;</span></div>

接着我嘗試#" onclick="alert(/xss/),發現「也被轉義。

<div class="dev-name text-fixed" style="padding-right: 30px;" title="#&quot; onclick=&quot;alert(/xss/)"><span class="dev-name-text">#"&nbsp;onclick="alert(/xss/)</span></div><div class="">---</div>

遺憾的是這種xss沒法利用,不然和已有的csrf配合,加上一點社會工程,或者修改自己設備名稱,就可以到達比較不錯的攻擊效果。





DOS,堆棧溢出


同樣也是在/goform/fast_setting_wifi_set的ssid參數,其經過的sprintf函數,未對長度進行限制,這將導致堆棧溢出,而達到Dos拒絕服務的攻擊效果。





老固件版本的一個命令注入


在參考老固件版本時,我看到了一個函數疑似存在命令注入,但是由於我的Tenda Ax12設備固件已經升級,貌似無法回退,於是就打算qemu模擬下,但畢竟是模擬環境,和實體設備不太一樣,而且和網上其他的Tenda設備的模擬方式也不同。

在網上下載好老固件版本後,readelf確定好是mips大端序架構後,直接先嘗試直接qemu啟動。

可以正常啟動,但是實際上其監聽的ip實際上有點問題,雖然對這個命令注入漏洞測試驗證沒什麼影響,但是這個ip實際上我們是可以控制的。

通過之前啟動部分的分析,我們可以在binwalk解析出來的文件系統中,在/etc/config目錄下新建一個network文件,添加以下內容。

config interface 'loopback' option ifname 'lo' option proto 'static' option ipaddr '127.0.0.1' option netmask '255.0.0.0'config globals 'globals'config interface 'lan' option type 'bridge' option proto 'static' option ipaddr '192.168.112.131' option netmask '255.255.255.0'

只要我們想修改listen的ip,就去修改ipaddr的值就行了,重新qemu啟動效果如下。

可以看到監聽ip就變成了我們設置的ip了,環境就差不多模擬好了,雖然和真實設備有差別,但是能正常接收http請求。

我是在fast_setting_internet_set接口的處理函數sub_431AD8中的一個子函數sub_42581發現了這個命令注入漏洞。

其先是從websRec結構體a1,調用websGetVar函數獲取到了staticIp的值,然後用sprintf將其給到v5,然後調用doSystemCmd_route執行命令,我們可以通過控制傳入staticIp的值來達到命令注入的效果。

開始編寫exp,攻擊效果如下,我的exp是將裝有密碼的admin配置文件給copy到了tmp目錄下 並命名為hack。





新固件的一個命令注入


這個漏洞發生在/goform/setMacFilterCfg接口,其對應的處理函數為sub_424334,本來這是一個存在棧溢出的函數,但是我偶然發現,其可能會存在命令注入。

在這個函數的最下面,有這樣的代碼。

可以看到,其很危險的調用了doSystemCmd函數,只為了輸出一段話到/tmp/macfilter文件,仔細觀察後,v2是傳入的macFilterType參數,根據printf的那句話,&v14[2]是不是就是指向上一次macFilterType的值呢,如果能控制這個參數,就有可能造成命令注入。

這個接口對應了後台管理界面->高級功能->MAC地址過濾,macFilterType參數就是對應了白名單和黑名單,也就是write和black。先進行一次訪問,但是我將macFilterType的值修改為test。

緊接着,繼續再正常隨便訪問一次,發現&v14[2]的值就是test。

這意味着,確實存在命令注入。

根據這個我嘗試編寫腳本進行攻擊,主要是重啟命令,恢復出廠設置命令,和/bin/sh命令,攻擊效果如下。

重啟命令和恢復出廠設置,都起了效果,但是/bin/sh卻未成功,但是經過調試發現,確實進入dosystemcmd函數前的參數是/bin/sh,但不知道為什麼沒成功getshell。

無論怎樣,這個地方確實存在着命令注入,雖然需要一次身份驗證,但是危害性還是較強。





參考


《揭秘家用路由器0day漏洞挖掘技術》

Yaseng

https://yaseng.org/

H4lo-github

https://github.com/H4lo/IOT_Articles_Collection/blob/master/Collection.md

物聯網終端安全入門與實踐之玩轉物聯網固件上

https://www.freebuf.com/articles/endpoint/335030.html

物聯網終端安全入門與實踐之玩轉物聯網固件下

https://www.freebuf.com/articles/endpoint/344858.html

Tenda AC15 AC1900 Vulnerabilities Discovered and Exploited | by Sanjana Sarda | Independent Security Evaluators

https://blog.securityevaluators.com/tenda-ac1900-vulnerabilities-discovered-and-exploited-e8e26aa0bc68

路由器web服務架構

https://tttang.com/archive/1777/

Tenda Ax12系列分析

https://www.anquanke.com/post/id/255290


作者名片


END



往期熱門
(點擊圖片跳轉)

戳「閱讀原文」更多精彩內容!

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()