۱. مقدمه
آسیبپذیری CVE-2026-24061 یکی از ضعفهای امنیتی مهم شناساییشده در سرویس GNU Inetutils Telnetd است که میتواند به مهاجم راهدور اجازه دهد سازوکار احراز هویت سیستم را دور بزند و بدون داشتن نام کاربری یا رمز عبور معتبر، به سطح دسترسی روت (Root) دست پیدا کند. این آسیبپذیری بهدلیل مدیریت ناامن مقدار متغیر محیطی USER و انتقال آن به برنامه ورود سیستم به وجود آمده است. مهاجم میتواند با ارسال مقدار دستکاریشده، گزینههای خط فرمان مورد استفاده توسط برنامه login را تحت تأثیر قرار داده و فرایند احراز هویت را مختل کند.
اکسپلویت موفق این ضعف امنیتی میتواند به اجرای دستورات دلخواه، مشاهده یا تغییر فایلهای حساس، نصب بدافزار، ایجاد دسترسی پایدار و در نهایت تصاحب کامل سیستم آسیبپذیر منجر شود. در این سند، نحوه شکلگیری آسیبپذیری، شرایط لازم برای بهرهبرداری، ساختار اکسپلویت، پیامدهای امنیتی و راهکارهای شناسایی و رفع آن بررسی میشود.
۱.۱ اطلاعات اجمالی آسیبپذیری
شماره آسیب پذیری: | CVE-2026-24061 |
امتیاز (CVSS): | 9.8 |
مولفه درگیر: | telnetd |
نوع آسیب پذیری: | Argument Injection Command-Line Argument Injection |
قابلیت در صورت اکسپلویت: | دور زدن کامل احراز هویت و دریافت دسترسی Root از راه دور، با قابلیت اجرای دستورات دلخواه، مشاهده یا تغییر تمامی فایلهای سیستم، نصب بدافزار، ایجاد دسترسی پایدار و تصاحب کامل سرور |
۱.۲ تحلیلات از پیش ارائه شده
نام تحلیلگر | تاریخ انتشار | لینک |
OffSec Team | Jan 30, 2026 | https://www.offsec.com/blog/cve-2026-24061 |
۱.۳ وضعیت Patch و نسخههای آسیبپذیر
نسخه | وضعیت |
قبل از 1.9.3 | در رکورد CVE به عنوان بازه اصلی آسیبپذیر نیامده است. |
1.9.3 تا 2.7 | آسیبپذیر |
2.8 و جدیدتر | مسیر رفعشده / نسخه پیشنهادی برای ارتقا |
در محیطهای عملیاتی باید نسخه بسته توزیع لینوکس هم بررسی شود. گاهی توزیعها patch امنیتی را به نسخه قدیمیتر backport میکنند، بنابراین تنها عدد نسخه upstream همیشه برای تصمیمگیری کافی نیست.
۲. خلاصه اجرایی
سرویس telnetd هنگام آغاز یک نشست Telnet، فرایند مذاکره پروتکل (Protocol Negotiation) را با کلاینت انجام میدهد. در این مرحله، کلاینت میتواند با استفاده از گزینههای ENVIRON یا NEW-ENVIRON برخی متغیرهای محیطی را برای سرور ارسال کند. در نسخههای آسیبپذیر، متغیر USER نیز از طریق این سازوکار پذیرفته شده و در محیط اجرای فرایند قرار میگیرد.
مشکل اصلی زمانی رخ میدهد که telnetd برای اجرای برنامه login یک رشته متنی شامل فرمان و آرگومانهای آن ایجاد میکند. در شرایط عادی، مقدار USER باید صرفاً بهعنوان یک داده، یعنی نام کاربری، پردازش شود؛ اما در پیادهسازی آسیبپذیر، این مقدار مستقیماً در رشته فرمان قرار میگیرد.
پس از ایجاد رشته فرمان، telnetd آن را با استفاده از تابع argcv_get تجزیه کرده و دوباره به آرایهای از آرگومانها یا argv تبدیل میکند. در نتیجه، اگر مقدار USER شامل فاصله، خط تیره یا رشتههایی مشابه گزینههای خط فرمان باشد، محتوای آن دیگر بهصورت یک مقدار واحد باقی نمیماند و میتواند به چند آرگومان اجرایی مستقل تبدیل شود. برای نمونه، در سناریوی آزمایشگاهی میتوان مقدار متغیر را بهصورت زیر تنظیم کرد:
USER="-f root"
این مقدار در ابتدا تنها یک رشته ذخیرهشده در محیط فرایند است؛ اما پس از قرار گرفتن در رشته فرمان و تجزیهشدن توسط argcv_get، به دو آرگومان جداگانه تبدیل میشود:
- -f
- root
از دید برنامه login، این مقادیر دیگر بخشی از یک نام کاربری نیستند. گزینه -f میتواند بهعنوان درخواست ورود سریع یا اعلام انجامشدن قبلی احراز هویت تفسیر شود و مقدار root نیز نام حساب کاربری هدف را مشخص میکند. به این ترتیب، دادهای که باید صرفاً بهعنوان نام کاربری پردازش میشد، به گزینه خط فرمان تبدیل شده و میتواند فرایند عادی احراز هویت را دور بزند. ریشه این آسیبپذیری، ترکیب ناامن دادههای کنترلشده توسط کاربر با یک رشته فرمان و تجزیه مجدد آن به آرگومانهای اجرایی است. این الگو از نظر مفهومی نوعی تزریق آرگومان (Argument Injection) خط فرمان محسوب میشود.
۳. توضیح فنی
Telnet client در مرحله مذاکره پروتکل (Protocol Negotiation)، با استفاده از گزینههای TELOPT_NEW_ENVIRON یا TELOPT_OLD_ENVIRON مقدار متغیرهای محیطی را برای telnetd ارسال میکند. این دادهها در تابع suboption واقع در فایل telnetd/state.c پردازش میشوند. در سناریوی آسیبپذیر، مقدار ارسالشده برای متغیر USER با استفاده از فراخوانی زیر داخل محیط (Environment) فرایند قرار میگیرد:
setenv("USER", "-f root", 1)
در ادامه، هنگام پردازش نگهدارنده مکان (Placeholder) مربوط به %U در تابع _var_short_name، مقدار USER از طریق getenv(“USER”) خوانده میشود. این مقدار سپس توسط تابع expand_line داخل login_invocation قرار میگیرد و بخشی از command string نهایی میشود. برای نمونه، اگر مقدار ‘USER’ برابر با f root- باشد، بخشی از command string ساختهشده به شکل زیر خواهد بود:
-f root
پس از آن، telnetd این command string را با استفاده از تابع argcv_get دوباره پارس (Parse) کرده و به argc و argv تبدیل میکند:
argcv_get(command, "", &argc, &argv)
در این مرحله، مقدار f root- دیگر یک string واحد محسوب نمیشود و به دو argument مستقل، یعنی f- و root، تقسیم میشود. در نهایت، برنامه مقصد با فراخوانی زیر اجرا میشود:
execv(argv[0], argv)
به این ترتیب، مقداری که در ابتدا فقط بهعنوان یک environment variable تحت کنترل کلاینت (Client) دریافت شده بود، پس از گسترش و پارسشدن مجدد، به آرگمانهای کامندلاین (Command-Line Argument) برنامه login تبدیل میشود. این فرایند باعث میشود optionهایی مانند f- مستقیماً به login منتقل شوند و در شرایط آسیبپذیر، مسیر عادی احراز هویت (Authentication) را تحت تأثیر قرار دهند.
۳.۱ مسیر داده آسیبپذیر
Telnet client
TELOPT_NEW_ENVIRON / TELOPT_OLD_ENVIRON
io_drain() [telnetd/utility.c]
telrcv() [telnetd/state.c]
suboption() [telnetd/state.c]
setenv(varp, valp, 1) [libc]
USER="-f root"
start_login() [telnetd/pty.c]
expand_line(login_invocation) [telnetd/utility.c]
_expand_block()
_expand_cond()
_expand_var()
_var_short_name()
getenv("USER") [libc, case 'U']
expanded command:
usr/bin/login ... -f root
argcv_get(cmd, "", &argc, &argv) [called from start_login()]
execv(argv[0], argv) [libc]
۴. آزمایشگاه
آزمایشگاه این پروژه، که همراه این گزارش ضمیمه شده، به صورت local-only طراحی شده است. هر دو سرویس داکر (Docker) فقط روی 127.0.0.1 بایند (Bind) میشوند و از بیرون میزبان قابل دسترسی نیستند. نسخه آسیبپذیر GNU Inetutils 2.7 از منبع (Source) بیلد گرفته میشود.
در این آزمایشگاه دو variant وجود دارد. variant اول recorder-telnetd است که مسیر login آن به باینری lab-login-recorder اشاره میکند و برای تایید (Verification) استفاده میشود و variant دوم original-login-telnetd است که از باینری login واقعی داخل کانتینر استفاده میکند.
سرویس compose | پورت میزبان | مسیر login کامپایلشده | کاربرد |
recorder-telnetd | 127.0.0.1:2323 | /usr/local/bin/lab-login-recorder | مسیر امن پیشفرض برای ثبت argv |
original-login-telnetd | 127.0.0.1:2324 | /bin/login | مقایسه با login واقعی داخل کانتینر |
۴.۱ نقشه فایلهای آزمایشگاه
مسیر | نقش |
evidence/ | CVE metadata، hashها، diffها، disassembly و traceهای GDB |
lab/Dockerfile | ساخت imageهای GNU Inetutils 2.7 با login path قابل تنظیم |
lab/docker-compose.yml | تعریف دو سرویس local-only |
lab/login-recorder.sh | ابزار امنی که فقط argv را چاپ و ثبت میکند |
lab/entrypoint.sh | ثبت lab-info و اجرای telnetd پشت socat |
scripts/local_verify.py | PoC |
۴.۲ اجرای recorder
همانطور که گفته شد دو داکر ایمیج با دو باینری مختلف بهعنوان باینری login در این آزمایشگاه قرار داده شده است. برای اجرای داکر ایمیج مربوط به recorder که صرفا اثبات کننده اجرای این اکسپلویت میباشد:
cd lab
env -u LD_PRELOAD docker compose up --build recorder-telnetd
env -u LD_PRELOAD python3 scripts/local_verify.py
۴.۳ خروجی مورد انتظار
فایل lab/output/recorder/login-recorder.log فقط خط argv را نگه میدارد و پیام تایید LAB_CVE_2026_24061_CONFIRMED توسط verifier تولید میشود، نه توسط recorder. این تفکیک باعث میشود ابزار login آزمایشگاه فقط مشاهدهگر باشد و منطق تایید در اسکریپت جدا بماند.
argv: [-p] [-h] [container-hostname] [-f] [root]
LAB_CVE_2026_24061_CONFIRMED
meaning=telnetd expanded remote USER data into login argv
۴.۴ اجرای original-login
برای اجرای داکر ایمیج مربوط به original-login که نمونه واقعی از انجام حمله میباشد:
cd lab
env -u LD_PRELOAD docker compose up --build original-login-telnetd
env -u LD_PRELOAD python3 scripts/original_login_root_check.py
این سرویس روی 127.0.0.1:2324 در میزبان bind میشود و telnetd آن با PATH_LOGIN=/bin/login بیلد شده است. برای توقف آزمایشگاه، از env -u LD_PRELOAD docker compose down در مسیر lab استفاده میشود. پس از توقف، docker compose ps باید هیچ سرویس فعالی را نشان ندهد.
۵. تحلیل کد و علت اصلی (Root Cause)
۵.۱ دریافت USER از Telnet ENVIRON
در نسخه 2.7، تابع suboption در telnetd/state.c گزینههای TELOPT_NEW_ENVIRON و TELOPT_OLD_ENVIRON را parse میکند. نام متغیر و مقدار آن از بافر suboption جدا میشوند و با setenv(varp, valp, 1) وارد محیط (Environment) فرایند میشوند. برای پیلود (Payload) آزمایشگاه، varp برابر USER و valp برابر f root- است. طبق ABI در معماری x86-64، فراخوانی setenv آرگومان اول را در rdi، آرگومان دوم را در rsi و آرگومان سوم را در edx دریافت میکند. بنابراین:
- rdi به USER اشاره دارد.
- rsi به f -rootاشاره دارد.
- edx به 1 اشاره دارد.
نکته مهم این است که telnetd در شروع کار یک unsetenv(“USER”) انجام میدهد، اما این کار کافی نیست، چون مذاکره (Negotiation) تلنت بعدا دوباره میتواند USER را از شبکه وارد محیط کند.
۵.۲ قالب آسیبپذیر login
telnetd برنامه login را مستقیما اجرا نمیکند؛ بلکه ابتدا خط فرمان اجرای آن را از روی یک رشته قالب میسازد. این قالب در فایل telnetd/telnetd.c و در متغیر سراسری login_invocation تعریف شده است. مقدار پیشفرض آن بهشکل زیر است:
PATH_LOGIN " -p -h %h %?u{-f %u}{%U}"
این رشته یک قالب شبیه به printf است. همانطور که پیشتر توضیح داده شد، هنگامی که یک نشست جدید آغاز میشود، تابع expand_line توکنهای موجود در قالب را با مقادیر واقعی جایگزین میکند. سپس رشته نهایی با استفاده از تابع argcv_get و بر اساس فاصلهها به آرایهای از آرگومانها، یعنی argv، تبدیل میشود. در پایان نیز telnetd با فراخوانی execv برنامه login را اجرا میکند.
۵.۲.۱ بخشهای ثابت قالب
- PATH_LOGIN: مسیر برنامه login است که هنگام build تعیین میشود. در آزمایشگاه recorder این مقدار برابر با مسیر زیر است:
/usr/local/bin/lab-login-recorder
در آزمایشگاه original-login نیز مقدار آن برابر است با:
/bin/login
۵.۲.۲ آرگومانهای فرمتینگ
- p- به برنامه login میگوید environment فعلی را حفظ کند.
- h %h- نام میزبان راهدور را به برنامه loginارسال میکند. در این عبارت، h% هنگام اجرای expand_line با نام واقعی میزبان جایگزین میشود.
- u{-f %u}{%U}?% ساختار کلی این عبارت بهشرح زیر است:
%?متغیر{اگر مقدار داشت}{اگر مقدار نداشت}
بررسی میکند که آیا متغیر داخلی %u، یعنی user_name، مقدار دارد یا خیر:
- اگر داشت:
-f %u
- اگر نداشت:
%U
و U% با مقدار متغیر محیطی USER جایگزین میشود.
Token | معنا |
u% | نام کاربر احراز شده یا autologin معتبر |
U% | مقدار environment با نام USER |
u{A}{B}?% | اگر %u موجود باشد A، وگرنه B |
در مسیر آزمایشگاه، autologin معتبر وجود ندارد. پس شرط %u برابر با false میشود و شاخه {U%} اجرا میشود. چون USER برابر f root- است، command string نهایی شامل همین دو token میشود. منظور از دو توکن دو آرگومان f- و root- میباشد.
۵.۳ تبدیل command string به argv
تابع start_login در telnetd/pty.c ابتدا scrub_env را اجرا میکند، سپس expand_line(login_invocation) را صدا میزند. خروجی expand_line یک رشته (String) است. قطعه کد زیر، شامل تابع مربوط به انجام فرایند لاگین بهواسطه command line آلوده لاگین میباشد.
void
start_login (char *host, int autologin, char *name)
{
char *cmd;
int argc;
char **argv;
(void) host; /* Silence warnings. Diagnostic use? */
(void) autologin;
(void) name;
scrub_env (); /* Set the environment variable "LINEMODE" to indicate our linemode */
if (lmodetype == REAL_LINEMODE)
setenv ("LINEMODE", "real", 1);
else if (lmodetype == KLUDGE_LINEMODE || 1modetype == KLUDGE_OK)
setenv ("LINEMODE", "kludge", 1);
cmd = expand_line (login_invocation);
if (!cmd)
fatal (net, "can't expand login command line");
argcv_get (cmd, "", &argc, &argv);
execv(argv[0], argv);
syslog (LOG_ERR, "%s: %m\n", cmd);
fatalperror (net, cmd);
}
سپس:
argcv_get(cmd, "", &argc, &argv)


