1. ما يجب فهمه أولاً
عند بناء تطبيقات Windows أو خدمات Windows، تظهر أحياناً حالات تريد فيها «تنفيذ هذه المعالجة فقط بصفة مستخدم آخر».
على سبيل المثال، في المواقف التالية:
- الرغبة في الوصول إلى خادم الملفّات بصلاحيّات المستخدم نفسه من داخل خدمة Windows
- الرغبة في التحقّق، من تطبيق إداريّ، من النطاق الذي يراه مستخدم معيّن فقط
- الرغبة في تنفيذ جزء من المعالجة بصلاحيّات المستخدم المستدعي، عبر Named Pipe أو RPC أو COM أو IIS أو ASP.NET Core وما شابه
- الرغبة في تبديل حساب Windows المستخدَم حسب وحدة المعالجة، بحكم ظروف الأصول القائمة
هنا يظهر مفهوم انتحال الهوية، ورمز الوصول، ورمز انتحال الهوية.
لكن ثمّة أمراً أودّ التأكيد عليه أوّلاً هنا.
انتحال الهوية في Windows ليس «سحراً يجعلك مسؤولاً». إنّه آليّة تبدّل، على مستوى مؤشّر الترابط بشكل أساسيّ، السياق الأمنيّ المستخدَم في فحص الوصول.
إذا نُفِّذ دون فهم هذا الفرق، تحدث مشكلات من هذا القبيل:
- تظنّ أنّك انتحلتَ الهوية، لكنّ الوصول إلى الملفّ يصبح
Access denied - تستطيع قراءة الملفّات المحليّة، لكنّ مشاركة الشبكة وحدها تفشل
- في منتصف
Task.Runأوasync، تجد نفسك عدتَ إلى المستخدم الأصليّ دون أن تشعر - يستمرّ الانتحال حتّى مرحلة تسجيل السجلّات (logs) والمعالجة اللاحقة، فتصبح حدود الصلاحيّات غير واضحة
- الخلط بين الرمز الأساسيّ ورمز انتحال الهوية، فيفشل إطلاق العمليّة
- التعثّر عند سؤال «المستخدم عضو في Administrators، فلماذا لا يمكنه الكتابة؟»
يرتّب هذا المقال طريقة التفكير اللازمة للتعامل الآمن مع رموز انتحال الهوية في Windows في العمل الفعليّ.
هذا ليس حديثاً عن أساليب الهجوم أو انتزاع الصلاحيّات. إنّه حديث عن كيفيّة التعامل الصحيح مع حدود الصلاحيّات في تطبيقات Windows وخدمات Windows وتطبيقات .NET.
كما أنّ الشيفرة الواردة في هذا المقال منشورة على GitHub كمجموعة نماذج قابلة للبناء (مكتبة، وعرض توضيحيّ يعمل على Windows، واختبارات وحدة للتحقّق من الوسائط وسلوك الحماية).
windows-impersonation-token - komurasoft-blog-samples (GitHub)
2. ما هو رمز الوصول
في Windows، يُستخدَم رمز الوصول لتمثيل السياق الأمنيّ للمستخدم أو العمليّة.
يحتوي رمز الوصول تقريباً على المعلومات التالية.
- SID الخاصّ بالمستخدم
- المجموعات المنتَمى إليها
- الصلاحيّات (Privilege)
- المالك الافتراضيّ
- DACL الافتراضيّة
- SID المقيَّدة
- مستوى النزاهة (integrity level)
- حالة الرفع (elevation)
- مستوى الانتحال
- نوع الرمز
الكثير من كائنات Windows، مثل الملفّات والسجلّ والخدمات والأنابيب المسمّاة والعمليّات ومؤشّرات الترابط والأحداث والـ mutex، تمتلك واصفاً أمنيّاً (security descriptor).
عندما يحاول مؤشّر ترابط ما فتح كائن محميّ، تقارن Windows معلومات الرمز مع ACL الخاصّة بالكائن الهدف.
مؤشّر الترابط الذي يقوم بالعمليّة
↓
بأيّ سياق أمنيّ يتمّ الوصول؟
↓
النظر إلى مستخدم الرمز، ومجموعاته، وصلاحيّاته
↓
المقارنة مع ACL الخاصّة بالكائن الهدف
↓
تحديد السماح / الرفض
فهم «بأيّ سياق أمنيّ يتمّ الوصول» هذا هو المدخل لفهم رمز انتحال الهوية.
3. للعمليّة رمز أساسيّ
لكلّ عمليّة في Windows عادةً رمز وصول أساسيّ (Primary Access Token).
على سبيل المثال، عندما يُطلِق مستخدم تطبيقاً من سطح المكتب، يُلحَق بتلك العمليّة رمز أساسيّ يمثّل السياق الأمنيّ لذلك المستخدم.
أمّا خدمة Windows، فيُلحَق بها رمز أساسيّ خاصّ بحساب تشغيل الخدمة.
مثلاً بهذا الشكل التصوّريّ.
MyService.exe
Primary Token: DOMAIN\svc-app
عندما لا يقوم مؤشّر الترابط داخل هذه الخدمة بأيّ انتحال خاصّ، فإنّه يستخدم عند الوصول إلى الملفّات أو السجلّ الرمزَ الأساسيّ الخاصّ بالعمليّة.
بعبارة أخرى، الوضع الافتراضيّ هو كالتالي.
Thread A
Impersonation Token: لا يوجد
↓
يستخدم فحص الوصول الرمز الأساسيّ الخاصّ بالعمليّة (Process)
في هذه الحالة، عند فتح C:\Data\foo.txt، يُنظَر فيما إذا كان DOMAIN\svc-app يملك صلاحيّة الوصول.
4. رمز انتحال الهوية يُلحَق بمؤشّر الترابط
عندما يبدأ الانتحال، يُلحَق بمؤشّر الترابط رمز انتحال الهوية. وهذه هي النقطة المهمّة: الانتحال ليس، من حيث الأساس، «تحوّل العمليّة بأكملها إلى مستخدم آخر»، بل الأدقّ أن نعتبره خضوع ذلك المؤشّر تحديداً لفحص الوصول ضمن سياق أمنيّ مختلف.
MyService.exe
Primary Token: DOMAIN\svc-app
Thread A
Impersonation Token: DOMAIN\alice
Thread B
Impersonation Token: لا يوجد
في هذه الحالة، عندما يفتح Thread A ملفّاً، يُفحَص الوصول بصلاحيّات DOMAIN\alice.
في المقابل، لمّا كان Thread B لا يقوم بالانتحال، يُفحَص وصوله بصلاحيّات DOMAIN\svc-app.
عدم فهم هذا الفرق يولّد هذا النوع من الالتباس.
// ظننّا أننا انتحلنا الهوية في Thread A
StartImpersonation(token);
// لكن المعالجة أُلقيت إلى مؤشّر ترابط آخر
Task.Run(() =>
{
File.ReadAllText(path);
});
// ثمّ أُعيد الوضع فوراً
RevertToSelf();
في هذه الحالة، ليس مضموناً أنّ مؤشّر الترابط الذي يقرأ الملفّ فعليّاً يعمل ضمن حالة الانتحال المتوقَّعة.
يجب التعامل مع الانتحال مع توضيح علاقته بالنطاق (scope) ومؤشّر الترابط والمعالجة غير المتزامنة.
5. «الانتحال» ليس رفع صلاحيّات
كلمة «الانتحال» قد تبدو قويّة نوعاً ما، لكنّ المهمّ في العمل الفعليّ هو ألّا نخلط بينها وبين «رفع الصلاحيّات».
ما يمكن فعله عبر الانتحال هو أساساً هذا:
المعالجة تجري بصلاحيّات عمليّة الخادم
↓
جزء محدود فقط من المعالجة يخضع لفحص الوصول بصلاحيّات مستخدم العميل
على سبيل المثال، إذا رغبتَ في استخدام ACL الخاصّة بخادم الملفّات كما هي كوسيلة للتحكّم بالصلاحيّات، فإنّ قراءة تطبيق الخادم الملفّات دائماً بحساب الخدمة يمنع انعكاس ACL الخاصّة بكلّ مستخدم على حدة.
لذا، يُنتحَل جزء فقط من معالجة الطلب بصفة المستخدم المستدعي، وتُجرى عمليّة الوصول إلى الملفّ.
طلب HTTP / RPC / Named Pipe
المستخدم: DOMAIN\alice
↓
تطبيق الخادم
العمليّة: DOMAIN\svc-app
↓
جزء الوصول إلى الملفّ فقط يُنتحَل بصفة DOMAIN\alice
↓
السماح / الرفض عبر ACL الخاصّة بخادم الملفّات
هذا مفيد عندما تريد استخدام ACL القائمة في Windows بدل قرار صلاحيّات خاصّ بالتطبيق.
لكن، رغم أنّ الانتحال مفيد، فإنّ إخطاءً في التصميم يجعل حدود الصلاحيّات غير واضحة.
- أيّ معالجة تُنفَّذ بصفة مَن
- أين بدأ الانتحال
- أين يُعاد الوضع فعليّاً وبشكل مؤكَّد
- بصلاحيّات أيّ مستخدم تُصدَر أيّ سجلّات
- هل يُعاد الوضع أيضاً عند حدوث استثناء
- هل يبقى الانتحال ساري المفعول حتّى نهاية المعالجة غير المتزامنة
من المهمّ توضيح هذه الجوانب بالشيفرة.
6. الفصل بين الرمز الأساسيّ ورمز انتحال الهوية
من أكثر ما يُلتبَس بشأن رموز Windows بشكل خاصّ هو الرمز الأساسيّ ورمز انتحال الهوية.
يمكن التفكير فيهما تقريباً كالتالي.
| الرمز | الاستخدام الرئيسيّ | مثال نموذجيّ |
|---|---|---|
| الرمز الأساسيّ | يمثّل السياق الأمنيّ للعمليّة | إطلاق العمليّات، CreateProcessAsUser |
| رمز انتحال الهوية | يُستخدَم كي يعمل مؤشّر الترابط ضمن سياق أمنيّ مختلف | ImpersonateLoggedOnUser، SetThreadToken، انتحال عميل Named Pipe |
الأهمّ بشكل خاصّ هو أنّه عند الرغبة في إطلاق عمليّة، يلزم من حيث المبدأ رمز أساسيّ.
مجرّد امتلاك رمز انتحال هوية لا يعني بالضرورة إمكانيّة استخدامه مباشرةً في إطلاق عمليّة لمستخدم آخر.
نموذجيّاً، يسير التدفّق كالتالي.
انتحال هوية العميل
↓
الحصول على رمز انتحال الهوية عبر OpenThreadToken
↓
إنشاء رمز أساسيّ عبر DuplicateTokenEx
↓
تمريره إلى CreateProcessAsUser وما شابه
على العكس، إن كنتَ تريد فقط «الوصول إلى الملفّ من هذا المؤشّر بصفة مستخدم آخر»، فالأمر يتعلّق باستخدام رمز انتحال الهوية، لا بإطلاق عمليّة.
الخلط بين هذين يوقعك، رغم صحّة وسائط الـ API، في حيرة أمام أخطاء من نوع Access denied أو The parameter is incorrect.
7. فهم مستويات الانتحال
يملك رمز انتحال الهوية مستوى انتحال.
هناك بشكل نموذجيّ 4 مستويات.
| مستوى الانتحال | المعنى التقريبيّ |
|---|---|
| Anonymous | لا يستطيع الخادم الحصول على معلومات تعريف العميل |
| Identification | يستطيع الخادم تحديد هويّة العميل، لكن لا يمكن استخدامه للوصول إلى الكائنات بصلاحيّات ذلك العميل |
| Impersonation | يستطيع الخادم العمل على النظام المحليّ بصلاحيّات العميل |
| Delegation | يستطيع الخادم تفويض صلاحيّات العميل حتّى تجاه أنظمة بعيدة |
ما يتعثّر فيه العمل الفعليّ كثيراً هو الفرق بين Identification وImpersonation.
Identification كما يوحي اسمه، هو مستوى لمعرفة هويّة الطرف الآخر.
وهو غير كافٍ لغرض مثل فتح ملفّ بصلاحيّات ذلك المستخدم.
لذا يحدث هذا:
WindowsIdentity.GetCurrent().Name يظهر باسم المستخدم المتوقَّع
↓
لكنّ الوصول إلى الملفّ يصبح Access denied
في هذه الحالة، لا يكفي النظر إلى الاسم وحده، بل يلزم التحقّق أيضاً من مستوى الانتحال.
في .NET، يمكن الاستعانة بـ WindowsIdentity.ImpersonationLevel كدليل.
using System.Security.Principal;
WindowsIdentity identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);
Console.WriteLine(identity.ImpersonationLevel);
في الوصول عبر الشبكة، يلزم مزيد من الحذر.
في تصميمات من نوع «انتحال هوية مستخدم عند خادم ويب، ثمّ الوصول بصفة ذلك المستخدم إلى خادم ملفّات أو خادم قاعدة بيانات آخر»، قد تصطدم بما يُعرَف بمشكلة القفزة المزدوجة (double hop).
في هذه الحالة، لا يُضمَن أنّ مجرّد الانتحال في شيفرة التطبيق يحلّ المشكلة. يلزم تصميم يشمل Kerberos وSPN والتفويض والتفويض المقيَّد (constrained delegation) وحساب الخدمة وطريقة مصادقة الجهة المتّصلة.
8. الشكل الأساسيّ للانتحال
الشكل المفاهيميّ عند التعامل مع الانتحال عبر Win32 API هو كالتالي.
1. الحصول على الرمز المُستخدَم في الانتحال
2. انتحال مؤشّر الترابط الحاليّ بهذا الرمز
3. تنفيذ المعالجة اللازمة فقط
4. العودة حتماً إلى السياق الأمنيّ الأصليّ
5. إغلاق مقبض الرمز
من حيث شكل الشيفرة، يجب أن يكون دائماً ضمن try / finally.
if (!ImpersonateLoggedOnUser(tokenHandle))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
try
{
// هذا الجزء فقط يُنفَّذ بصفة المستخدم المُنتحَل
DoWorkAsImpersonatedUser();
}
finally
{
if (!RevertToSelf())
{
// حالة تعذّر العودة خطيرة، فلا يجوز على الأقل الاستمرار في المعالجة
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
المهمّ ليس بدء الانتحال بقدر ما هو العودة بشكل مؤكَّد. فنسيان العودة يجعل المعالجة اللاحقة في ذلك المؤشّر تعمل بصفة المستخدم المُنتحَل.
في التطبيقات التي تستخدم مجمّع مؤشّرات الترابط (thread pool) بشكل خاصّ، قد تؤثّر معالجة كان يُقصَد بها أن تكون معالجة واحدة على طلب آخر أو معالجة أخرى.
لذا يجب النظر إلى الانتحال ليس على أنّه «ابدأ ثمّ أَعِد»، بل احصره ضمن نطاق صغير.
9. في .NET استخدم WindowsIdentity.RunImpersonated
في .NET، إن أمكن، فاستخدام WindowsIdentity.RunImpersonated يجعل التعبير عن نطاق الانتحال في الشيفرة أسهل.
إن كان لديك SafeAccessTokenHandle، يمكن كتابته كالتالي.
using Microsoft.Win32.SafeHandles;
using System.Security.Principal;
static string ReadFileAsUser(SafeAccessTokenHandle token, string path)
{
return WindowsIdentity.RunImpersonated(token, () =>
{
return File.ReadAllText(path);
});
}
الميزة الجميلة في هذا الشكل هي أنّ نطاق الانتحال محصور داخل تعبير لامدا.
RunImpersonated(token, () =>
{
// هذا الجزء فقط منتحَل
});
// من هذه النقطة إلى الخارج، السياق الأصليّ
عند رغبتك في انتحال معالجة محدَّدة فقط، كالوصول إلى الملفّات أو السجلّ أو استدعاء مكتبة قائمة، فإنّ هذا الشكل سهل القراءة وآمن.
في المعالجة غير المتزامنة، استخدم RunImpersonatedAsync.
using Microsoft.Win32.SafeHandles;
using System.Security.Principal;
static Task WriteFileAsUserAsync(
SafeAccessTokenHandle token,
string path,
string text,
CancellationToken cancellationToken)
{
return WindowsIdentity.RunImpersonatedAsync(token, async () =>
{
await File.WriteAllTextAsync(path, text, cancellationToken);
});
}
ما يجب تجنّبه هو إطلاق مهمّة من نوع fire-and-forget من داخل نطاق الانتحال.
// مثال سيّئ
WindowsIdentity.RunImpersonated(token, () =>
{
_ = Task.Run(() =>
{
File.WriteAllText(path, text);
});
});
هذه الشيفرة تجعل من الصعب معرفة متى، وضمن أيّ سياق تنفيذ، تجري «المعالجة التي تكتب الملفّ فعليّاً».
المعالجة غير المتزامنة التي تحتاج إلى الانتحال يجب أن تُنتظَر (await) داخل RunImpersonatedAsync، وأن يُخرَج من النطاق فقط بعد اكتمال المعالجة.
10. عند الحصول على رمز عبر LogonUser
من أشهر واجهات الـ API للحصول على رمز من بيانات اعتماد مستخدم آخر هي LogonUser، لكنّها API يجب التعامل معها بحذر.
يُمرَّر إلى LogonUser اسم المستخدم والنطاق (domain) وكلمة المرور.
أي أنّ التطبيق نفسه يتولّى التعامل مع بيانات الاعتماد.
في العمل الفعليّ، ننتبه إلى هذه النقاط.
- عدم وضع كلمة المرور بنصّ صريح في الشيفرة أو ملفّات الإعداد
- الاعتماد قدر الإمكان على مصادقة نظام التشغيل، وحساب الخدمة، والتفويض، ومصادقة Windows القائمة
- إدارة المعلومات السرّيّة عبر مخزن أسرار (Secret Store) مناسب أو بنية تشغيليّة ملائمة
- إغلاق مقبض الرمز دائماً
- عدم إخراج معلومات سرّيّة غير اسم المستخدم في السجلّات
- تقليل نطاق الانتحال إلى أدنى حدّ
نورد مثالاً بأدنى بِنية.
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
internal static class NativeMethods
{
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LogonUser(
string lpszUsername,
string? lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out SafeAccessTokenHandle phToken);
public static SafeAccessTokenHandle Logon(
string userName,
string? domain,
string password)
{
bool ok = LogonUser(
userName,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
out SafeAccessTokenHandle token);
if (!ok)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return token;
}
}
public static string ReadFileWithExplicitCredential(
string userName,
string? domain,
string password,
string path)
{
using SafeAccessTokenHandle token = NativeMethods.Logon(userName, domain, password);
return WindowsIdentity.RunImpersonated(token, () =>
{
return File.ReadAllText(path);
});
}
هذا المثال ليس إلّا لتوضيح شكل الـ API.
في العمل الفعليّ، يُبحَث أوّلاً في «هل من الصحيح أصلاً أن يكون التصميم بحيث يستلم التطبيق كلمة المرور».
في كثير من الحالات، تكون هذه البدائل أكثر أماناً.
| ما تريد فعله | البديل |
|---|---|
| رغبة الخدمة بأكملها في الوصول إلى مورد محدَّد | إسناد ACL بالحدّ الأدنى الضروريّ لحساب خدمة مخصَّص |
| الوصول إلى الملفّات بصلاحيّات المستخدم نفسه | استخدام مصادقة Windows وتصميم تفويض |
| تنفيذ عمليّة تحتاج صلاحيّات مسؤول | إنشاء API إداريّة واضحة في جانب الخدمة، والتحكّم عبر تصريح جانب التطبيق |
| رغبة جزء فقط من المعالجة في حساب مختلف | حصر نطاق الانتحال على مستوى الدالّة (method)، والإبقاء على سجلّ تدقيق (audit log) |
11. الانتباه إلى نوع تسجيل الدخول في LogonUser
تختلف طبيعة الرمز الذي يُحصَل عليه من LogonUser باختلاف نوع تسجيل الدخول (Logon Type).
بشكل خاصّ، إن نسخت الشيفرة دون فهم هذا الفرق، فلن تعمل كما هو متوقَّع.
| نوع تسجيل الدخول | ملاحظة |
|---|---|
| Interactive | قريب من تسجيل الدخول التفاعليّ. سهل الاستخدام في العمليّات المحليّة، لكنّه يعتمد على بيئة التنفيذ والصلاحيّات |
| Network | موجَّه لتسجيل الدخول عبر الشبكة. قد لا يكون الرمز المُعاد قابلاً للاستخدام مباشرةً في إطلاق العمليّات |
| NewCredentials | محليّاً يبقى قريباً من بيانات الاعتماد الحاليّة، ويُستخدَم أحياناً لاستخدام بيانات اعتماد محدَّدة عند الاتّصال البعيد |
ما نريد قوله هنا ليس حفظ نوع تسجيل دخول محدَّد عن ظهر قلب.
المهمّ هو نقطتان.
- يختلف السلوك في الوصول المحليّ والوصول عبر الشبكة وإطلاق العمليّات باختلاف نوع تسجيل الدخول
- يلزم التحقّق ممّا إذا كان الرمز المُعاد رمزاً أساسيّاً أم رمز انتحال هوية
على سبيل المثال، تمرير رمز حُصِل عليه بـ LOGON32_LOGON_NETWORK مباشرةً إلى CreateProcessAsUser وفشل التنفيذ، هو نوع نموذجيّ من الالتباس.
إن كان الهدف إطلاق عمليّة، فيلزم رمز أساسيّ.
يصبح التصميم عندئذٍ هو إنشاء رمز أساسيّ عبر DuplicateTokenEx حسب الحاجة.
12. لا تستهن بـ RevertToSelf
عند بدء الانتحال عبر Win32 API، تُنهي RevertToSelf الانتحال. هذه العودة ليست مجرّد ترتيب لاحق، بل معالجة مهمّة لإعادة الحدّ الأمنيّ إلى وضعه الأصليّ.
مثال سيّئ.
ImpersonateLoggedOnUser(token);
DoWork();
RevertToSelf();
قد يبدو للوهلة الأولى غير مشكِل، لكن إن حدث استثناء داخل DoWork()، فلن يُستدعَى RevertToSelf().
اجعله دائماً داخل finally.
if (!ImpersonateLoggedOnUser(token))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
try
{
DoWork();
}
finally
{
if (!RevertToSelf())
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
الاستمرار في المعالجة عند فشل RevertToSelf خطير أيضاً. فمواصلة المعالجة اللاحقة في حالة قد لا تكون قد عادت فيها إلى الصلاحيّات الأصليّة يعني استمرار المعالجة بصلاحيّات مستخدم غير مقصود.
RunImpersonated / RunImpersonatedAsync في .NET مفيدة كتعبير عن النطاق يتفادى نسيان هذه العودة.
13. اجعل نطاق الانتحال صغيراً بقدر الإمكان
أهمّ مبدأ تصميميّ في الانتحال هو انتحال المعالجة الضروريّة فقط.
مثال سيّئ.
WindowsIdentity.RunImpersonated(token, () =>
{
ValidateRequest();
LoadConfiguration();
WriteDebugLog();
ReadUserFile();
UpdateDatabase();
SendNotification();
});
انتحال نطاق واسع بهذا الشكل يجعل من الصعب معرفة أيّ عمليّة تتمّ بأيّ صلاحيّات.
مثلاً، قد يُجرى الوصول إلى وجهة تسجيل السجلّات بصلاحيّات المستخدم المُنتحَل، فيفشل تسجيل السجلّ. وقد يُحاوَل الاتّصال بقاعدة البيانات ببيانات اعتماد المستخدم المُنتحَل بدل حساب الخدمة. وقد تتأثّر معالجة الإشعارات أو إنشاء الملفّات المؤقّتة أيضاً بسياق صلاحيّات غير ضروريّ.
المثال الجيّد هو فصل العمليّات التي تحتاج فعلاً إلى الانتحال فقط.
ValidateRequest();
LoadConfiguration();
string content = WindowsIdentity.RunImpersonated(token, () =>
{
return File.ReadAllText(userFilePath);
});
UpdateDatabase(content);
WriteAuditLog(userName, userFilePath, success: true);
بهذا الشكل يتّضح أنّ الانتحال ضروريّ فقط لجزء File.ReadAllText.
الانتحال مفيد، لكن كلّما اتّسع، أصبح أصعب قراءةً وأكثر عرضةً للحوادث.
14. في المعالجة غير المتزامنة، تحقّق من «هل استمرّ الانتحال حتّى النهاية»
في تطبيقات .NET الحديثة، تكون الكثير من العمليّات غير متزامنة، كالملفّات وHTTP وقاعدة البيانات والطوابير والتخزين.
لذا يحتاج الجمع بين الانتحال وasync / await إلى حذر.
السياسة الأساسيّة هي فقط هذه:
المعالجة غير المتزامنة التي تحتاج إلى الانتحال، تُنتظَر (await) داخل RunImpersonatedAsync
مثال جيّد.
await WindowsIdentity.RunImpersonatedAsync(token, async () =>
{
await using FileStream stream = File.OpenRead(path);
using var reader = new StreamReader(stream);
string text = await reader.ReadToEndAsync();
await ProcessTextAsync(text);
});
لكن حتّى بهذا الشكل، هناك ما ينبغي التفكير فيه.
هل تحتاج ProcessTextAsync نفسها أيضاً إلى العمل بصفة المستخدم المُنتحَل؟
إن كان يكفي انتحال جزء قراءة الملفّ فقط، فمن الأسلم الفصل هكذا.
string text = await WindowsIdentity.RunImpersonatedAsync(token, async () =>
{
return await File.ReadAllTextAsync(path);
});
await ProcessTextAsync(text);
كون بإمكانك تنفيذ await داخل نطاق الانتحال لا يعني أنّه يجوز إدراج أيّ شيء فيه.
حتّى في المعالجة غير المتزامنة، قلِّل نطاق الانتحال إلى أدنى حدّ.
15. ASP.NET Core والانتحال
عند استخدام مصادقة Windows في ASP.NET Core أيضاً، يلزم الحذر في التعامل مع الانتحال.
الافتراض بأنّه «بما أنّ تسجيل الدخول تمّ عبر مصادقة Windows، فإنّ معالجة الطلب بأكملها تعمل بصفة ذلك المستخدم» أمر خطير.
عموماً، تعمل عمليّة التطبيق نفسها بمعرّف مجمّع التطبيقات (application pool identity) أو حساب تشغيل الخدمة. تُحصَل هويّة Windows الخاصّة بالمستخدم كمعلومات مصادقة، لكن لا يُضمَن أنّ المعالجة بأكملها تصبح بصلاحيّات ذلك المستخدم مباشرةً.
إذا احتجتَ إلى تنفيذ إجراء معيّن بصلاحيّات المستخدم، فأنشئ النطاق صراحةً عبر RunImpersonated / RunImpersonatedAsync.
تصوّر الشيفرة كالتالي.
app.MapGet("/download", async (HttpContext context) =>
{
if (context.User.Identity is not WindowsIdentity user)
{
return Results.Unauthorized();
}
string path = GetPathFromRequest(context);
byte[] bytes = await WindowsIdentity.RunImpersonatedAsync(
user.AccessToken,
async () => await File.ReadAllBytesAsync(path));
return Results.File(bytes, "application/octet-stream");
});
في هذا المثال أيضاً، المُنتحَل هو جزء قراءة الملفّ فقط.
هل يلزم إدراج إنشاء الاستجابة والسجلّات وقرار التصريح الخاصّ بجانب التطبيق كلّها ضمن نطاق الانتحال؟ ينبغي التفكير في ذلك بعناية.
16. كيفيّة قراءة Access denied
الخطأ الشائع في التنفيذ الذي يستخدم الانتحال هو Access denied.
عند ظهور هذا الخطأ، فإنّ الاكتفاء بالتفكير فيه على أنّه «فشل الانتحال» يجعل الطريق أطول.
نُفرِّق الزوايا التي يجب التحقّق منها.
| الزاوية | ما يُتحقَّق منه |
|---|---|
| هل الانتحال يعمل فعلاً | التحقّق من WindowsIdentity.GetCurrent().Name داخل نطاق الانتحال |
| هل مستوى الانتحال كافٍ | هل هو المستوى المطلوب فعلاً، لا Identification |
| هل ACL الخاصّة بالمورد الهدف صحيحة | هل يملك المستخدم المُنتحَل صلاحيّة قراءة / كتابة |
| محليّ أم بعيد | هل ينجح في الملفّات المحليّة وتفشل UNC فقط |
| هل هي مشكلة القفزة المزدوجة | هل تحاول الذهاب من خادم الويب إلى خادم الملفّات بصلاحيّات المستخدم |
| هل خرجتَ من نطاق الانتحال | هل الإدخال/الإخراج الفعليّ يعمل خارج النطاق أو في مهمّة أخرى |
| هل نوع الرمز مناسب | هل تُمرِّر رمز انتحال هوية إلى إطلاق عمليّة |
| هل هناك تأثير من UAC / مستوى النزاهة | هل هو رمز غير مرفوع رغم عضويّة Administrators |
من المهمّ بشكل خاصّ ألّا تطمئنّ بالتحقّق من الاسم وحده.
WindowsIdentity identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);
هذا السجلّ مفيد، لكنّه غير كافٍ.
تحقّق على الأقلّ من هذا أيضاً.
Console.WriteLine(identity.ImpersonationLevel);
Console.WriteLine(identity.IsAuthenticated);
كذلك، إن كان الهدف مشاركة شبكة، فتحقّق ليس فقط من شيفرة التطبيق، بل أيضاً من طريقة المصادقة وإعدادات التفويض وSPN وحساب الخدمة وACL الخاصّة بخادم الملفّات.
17. UAC ومشكلة «مسؤول لكن يفشل»
في Windows، عضويّة المستخدم في مجموعة Administrators ليست الشيء نفسه كون الرمز الحاليّ مرفوعاً (elevated).
في البيئات التي يكون فيها UAC مفعَّلاً، تعمل العمليّة عادةً برمز مقيَّد حتّى للمستخدم المسؤول، وتحتاج العمليّات التي تتطلّب صلاحيّات مسؤول إلى الرفع.
لذا يحدث هذا:
المستخدم عضو في Administrators
↓
لكنّ الرمز الحاليّ غير مرفوع
↓
Access denied عند الكتابة في Program Files أو HKLM
الأمر نفسه ينطبق على الانتحال.
بدل الافتراض بأنّ «المستخدم المُنتحَل مسؤول، فمن المفترض أن يستطيع الكتابة»، يلزم التحقّق من الحالة الفعليّة للرمز الممرَّر.
عند التنقيح (debug)، نراجع هذه الزوايا.
- المجموعات المنتَمى إليها
- تفعيل / تعطيل الصلاحيّات (Privilege)
- مستوى النزاهة
- حالة الرفع
- هل هو رمز مقيَّد
- هل يوجد رمز مرفوع مرتبط (linked elevated token)
في Win32 API، يمكن استخدام GetTokenInformation للتحقّق من TokenType وTokenImpersonationLevel وTokenElevationType وTokenIntegrityLevel وغيرها.
لكن، من ناحية التصميم العمليّ، فإنّ حصر عمليّات محدَّدة بالحدّ الأدنى في خدمة مخصَّصة أو API إداريّة أكثر أماناً من «انتحال هوية مستخدم مسؤول لفعل أيّ شيء».
18. مشاركات الشبكة ومشكلة القفزة المزدوجة
من أكثر الاستفسارات شيوعاً بخصوص الانتحال هو الوصول إلى مشاركات الشبكة.
جهاز العميل
↓ مصادقة Windows
خادم الويب / خادم API
↓ الرغبة في الوصول بالانتحال
خادم الملفّات
في هذا الإعداد، قد يحدث أنّ «اسم المستخدم متاح على خادم الويب، لكن عند الذهاب إلى خادم الملفّات يفشل الأمر».
هذه مشكلة تتعلّق بإمكانيّة إعادة تفويض بيانات اعتماد المستخدم إلى خادم آخر.
الانتحال على الخادم المحليّ وتفويض خادم آخر ليسا الشيء نفسه.
قد لا يكفي مستوى Impersonation للتصرّف كعميل تجاه خادم بعيد، حتّى وإن كان صالحاً للعمليّات المحليّة.
إن أردتَ استخدام صلاحيّات المستخدم نفسه عبر الشبكة، فيلزم تصميم يشمل تفويض Kerberos والتفويض المقيَّد وSPN وحساب الخدمة وطريقة المصادقة.
من ناحية أخرى، قد لا تحتاج متطلّبات العمل أصلاً إلى الذهاب إلى خادم الملفّات بصلاحيّات Windows الخاصّة بالمستخدم نفسه.
في هذه الحالة، يكون هذا النوع من التصميم أبسط.
مصادقة وتصريح المستخدم يتمّان عبر التطبيق
↓
الوصول إلى خادم الملفّات يتمّ بحساب خدمة مخصَّص
↓
تسجيل معرّف المستخدم والملفّ الهدف في سجلّ العمليّات
هذا تصميم «يجعل قرار تصريح التطبيق هو الفيصل النهائيّ»، بدلاً من «جعل ACL الخاصّة بنظام التشغيل هي الفيصل النهائيّ».
أيّهما صحيح يعتمد على متطلّبات العمل.
لكن، إن لم يُوضَّح أيّ الطريقتين المختارة، فسيختلط الانتحال والتفويض وACL وتصريح التطبيق ويصبح الأمر غير مفهوم.
19. عمر مقبض الرمز
بما أنّ الرمز مقبض إلى كائن نواة (kernel object)، يجب إغلاقه فور عدم الحاجة إليه بعد الحصول عليه.
في .NET، الأساس هو استخدام SafeAccessTokenHandle وإدارة النطاق عبر using.
using SafeAccessTokenHandle token = NativeMethods.Logon(userName, domain, password);
string result = WindowsIdentity.RunImpersonated(token, () =>
{
return File.ReadAllText(path);
});
مثال سيّئ.
// مثال سيّئ: الاحتفاظ بالرمز بشكل عامّ (global) باستمرار
private static SafeAccessTokenHandle? _cachedToken;
الاحتفاظ بالرمز لمدّة طويلة يؤدّي إلى مشكلات من هذا القبيل.
- تسرّب المقابض (handle leak)
- عدم وضوح أيّ معالجة تستخدم أيّ رمز
- صعوبة توافق الأمر مع تعطيل الحساب أو تغيير الصلاحيّات
- يميل التصميم إلى الاحتفاظ ببيانات الاعتماد لفترة طويلة
- صعوبة الشرح من الناحية الرقابيّة (audit)
المبدأ هو كالتالي.
الحصول عليه عند الحاجة
↓
استخدامه ضمن أضيق نطاق
↓
إغلاقه دائماً
بالطبع، قد تكون هناك حالات يُنظَر فيها في التخزين المؤقّت (caching) بحسب تكلفة المصادقة أو متطلّبات التشغيل. لكن حتّى في تلك الحالة، يجب أن يشمل التصميم مدّة الصلاحيّة، والتخلّص منه، وتغيير الحساب، وسجلّ التدقيق، والتعامل عند تغيير الصلاحيّات.
20. ما يجب الاحتفاظ به في سجلّ التدقيق
في المعالجة التي تستخدم الانتحال، يكون تصميم السجلّ مهمّاً أيضاً.
على الأقلّ، الاحتفاظ بالمعلومات التالية في الجدول يسهّل التحقيق لاحقاً.
| البند | مثال |
|---|---|
| المستخدم الذي قدّم الطلب | DOMAIN\alice |
| حساب عمليّة التنفيذ | DOMAIN\svc-app |
| الحساب الذي جرى انتحاله | DOMAIN\alice أو حساب مخصَّص |
| المورد الهدف | مسار الملفّ، اسم المشاركة، مفتاح السجلّ، إلخ |
| العمليّة | Read، Write، Delete، CreateProcess، إلخ |
| النتيجة | Success، AccessDenied، Timeout، UnexpectedError |
| رمز الخطأ | رمز خطأ Win32، HRESULT، نوع الاستثناء |
| نطاق الانتحال | أيّ دالّة، وأيّ وحدة عمليّة جرى فيها الانتحال |
لكن هناك ما لا يجوز إخراجه في السجلّ.
- كلمة المرور
- قيمة رمز الوصول
- ترويسات المصادقة
- تذاكر Kerberos أو بيانات الاعتماد نفسها
- محتوى ملفّات يتضمّن معلومات شخصيّة
هدف السجلّ هو إمكانيّة تتبّع «مَن طلب، وبأيّ حساب، وماذا حاول، وكيف فشل أو نجح» لاحقاً.
ليست هناك حاجة لتسجيل بيانات الاعتماد نفسها.
21. الأنماط المضادّة الشائعة
نرتّب التنفيذات الخطرة الشائعة حول الانتحال.
21.1 انتحال التطبيق بأكمله
WindowsIdentity.RunImpersonated(token, () =>
{
RunEntireApplication();
});
انتحال التطبيق بأكمله يجعل من غير الواضح أيّ عمليّة تُنفَّذ بأيّ صلاحيّات.
اقصر الانتحال على عمليّات الإدخال/الإخراج أو استدعاءات API محدَّدة.
21.2 العودة دون finally
ImpersonateLoggedOnUser(token);
DoWork();
RevertToSelf();
خطير لأنّه لا يعود عند حدوث استثناء.
استخدم دائماً try / finally أو RunImpersonated.
21.3 تنفيذ fire-and-forget أثناء الانتحال
WindowsIdentity.RunImpersonated(token, () =>
{
_ = Task.Run(DoWorkAsync);
});
بما أنّ نطاق الانتحال يُخرَج منه قبل اكتمال المعالجة، فقد تعمل بصلاحيّات مختلفة عمّا هو متوقَّع.
إن احتجتَ إلى الانتحال، استخدم await داخل RunImpersonatedAsync.
21.4 الحكم على النجاح باسم المستخدم فقط
Console.WriteLine(WindowsIdentity.GetCurrent().Name);
حتّى وإن كان اسم المستخدم كما هو متوقَّع، فقد يكون مستوى الانتحال أو الصلاحيّات غير كافية.
تحقّق أيضاً من ImpersonationLevel، وACL الهدف، ونوع تسجيل الدخول، والتفويض عبر الشبكة.
21.5 انتحال حساب مسؤول كحساب مريح
تصميم من نوع «هذه المعالجة لا نريدها أن تفشل، لذا نستخدم انتحال حساب مسؤول» خطير.
من الأسلم تجهيز حساب مخصَّص بالحدّ الأدنى من الصلاحيّات اللازمة وتضييق نطاق العمليّة.
21.6 وضع كلمة المرور في ملفّ إعدادات
{
"UserName": "DOMAIN\\admin",
"Password": "P@ssw0rd!"
}
هذا أمر يجب تجنّبه.
عند التعامل مع بيانات الاعتماد، استخدم آليّة تناسب البيئة، مثل Secret Store أو Windows Credential Manager أو DPAPI أو Key Vault السحابيّ أو إدارة الأسرار في البنية التشغيليّة.
21.7 معاملة إطلاق العمليّة والوصول إلى الملفّات كموضوع واحد
قد يكفي رمز انتحال الهوية إن كان الأمر يتعلّق بالوصول إلى الملفّات فقط.
في المقابل، إن أردتَ إطلاق عمليّة بصفة مستخدم آخر، تظهر مسائل أخرى مثل الرمز الأساسيّ والملف الشخصيّ (profile) وسطح المكتب ومتغيّرات البيئة والجلسة والصلاحيّات.
عند استخدام CreateProcessAsUser أو CreateProcessWithTokenW، يجب معاملة الأمر كتصميم منفصل عن الانتحال.
22. زوايا الاختبار
قد يفوتك أشياء إذا اقتصر التحقّق من معالجة الانتحال على بيئة مسؤول محليّة فقط.
جهِّز على الأقلّ حالات الاختبار التالية.
| الحالة | ما يُتحقَّق منه |
|---|---|
| مستخدم يملك الصلاحيّة | يستطيع قراءة / كتابة الملفّ الهدف |
| مستخدم لا يملك الصلاحيّة | يفشل بصورة صحيحة باعتباره Access denied |
| مستخدم غير موجود | يُعامَل كفشل مصادقة |
| كلمة مرور خاطئة | يفشل دون إخراج معلومات سرّيّة في السجلّ |
| مشاركة الشبكة | التحقّق من فرق السلوك بين المحليّ وUNC |
| المعالجة غير المتزامنة | تعمل ضمن النطاق المتوقَّع حتّى بعد await |
| حدوث استثناء | يُلغى الانتحال حتماً |
| الطلبات المتزامنة | لا يختلط انتحال مستخدم آخر |
| تنفيذ الخدمة | يعمل بحساب الخدمة الفعليّ، لا ببيئة تسجيل دخول تفاعليّة للمطوّر |
بشكل خاصّ، لا يمكن الاستغناء عن هاتين النقطتين.
نجاح ما ينبغي أن ينجح
فشل ما ينبغي أن يفشل
في معالجة الانتحال، لا يُختبَر النجاح فقط، بل أيضاً الرفض المؤكَّد لمستخدم لا يملك الصلاحيّة.
إن نجحت عمليّة كان ينبغي رفضها، فقد يكون تصميم الانتحال أو التصريح خاطئاً.
23. قائمة تحقّق التنفيذ
راجع قائمة التحقّق هذه قبل التنفيذ وبعده.
| الزاوية | محتوى التحقّق |
|---|---|
| الغرض | هل يمكن تفسير سبب ضرورة الانتحال |
| البديل | هل جرى النظر في حساب خدمة أو تصريح تطبيقيّ بدل الانتحال |
| النطاق | هل نطاق الانتحال في أدنى حدوده |
| العودة | هل يعود بشكل مؤكَّد حتّى عند حدوث استثناء |
| المعالجة غير المتزامنة | هل يُنتظَر (await) حتّى الاكتمال داخل RunImpersonatedAsync |
| الرمز | هل الخلط بين الرمز الأساسيّ ورمز انتحال الهوية غير موجود |
| مستوى الانتحال | هل جرى النظر في الفرق بين Identification وImpersonation / Delegation |
| الشبكة | هل جرى التحقّق من UNC والقفزة المزدوجة وضرورة تفويض Kerberos |
| UAC | هل عضويّة المسؤول والحالة المرفوعة غير مخلوط بينهما |
| المعلومات السرّيّة | هل كلمة المرور غير محفوظة بنصّ صريح |
| المقبض | هل SafeAccessTokenHandle مُغلَق عبر using |
| السجلّ | هل يمكن تتبّع المستخدم والحساب المُنتحَل والهدف والنتيجة |
| الاختبار | هل جرى التحقّق من وجود الصلاحيّة / عدم وجودها / الاستثناء / التزامن |
إن كانت بنود كثيرة في هذه القائمة تعثّرت، فمن الأفضل مراجعة التصميم قبل كتابة الشيفرة.
24. تحديد موضع الاستخدام
رمز انتحال الهوية قويّ، لكنّه ليس الوسيلة التي ينبغي اختيارها أوّلاً دائماً.
من ناحية التصميم، التفكير حسب الاستخدام يقلّل الحيرة.
24.1 عند الرغبة في استخدام ACL الخاصّة بنظام التشغيل كما هي
إن كانت ACL الخاصّة بخادم الملفّات أو المجلّد المشترَك هي مركز قواعد العمل، وكان ينبغي للتطبيق أيضاً اتّباع ذلك القرار، فإنّ الانتحال بصفة المستخدم نفسه يكون ذا معنى.
الوصول بصفة المستخدم نفسه
↓
ACL الخاصّة بـ Windows هي الفيصل النهائيّ
في هذه الحالة، يُصمَّم الأمر بحيث يشمل مصادقة Windows ومستوى الانتحال والتفويض وإعداد الشبكة.
24.2 عند الرغبة في التصريح من جانب التطبيق
إن كانت قواعد العمل موجودة في جانب التطبيق، وكان خادم الملفّات أو قاعدة البيانات تحت إدارة التطبيق، فقد يكون الوصول بحساب الخدمة والتصريح من جانب التطبيق أوضح.
مصادقة المستخدم
↓
تصريح من جانب التطبيق
↓
الوصول إلى المورد بحساب الخدمة
↓
تسجيل معرّف المستخدم في سجلّ التدقيق
في هذه الطريقة، بدل استخدام الانتحال، يصبح منطق التصريح الخاصّ بالتطبيق وسجلّ التدقيق مهمّين.
24.3 عند الرغبة في تنفيذ عمليّات إداريّة
غالباً ما يكون تجهيز خدمة أو API مخصَّصة للعمليّات الإداريّة، تتولّى فيها التصريح والتحقّق من المُدخلات والتدقيق والتراجع (rollback)، أكثر أماناً من تنفيذ العمليّة الإداريّة مباشرةً برمز المستخدم.
العميل
↓
طلب إلى API الإدارة
↓
تصريح API الإدارة
↓
تنفيذ العمليّة بالحدّ الأدنى من الصلاحيّات اللازمة
↓
سجلّ التدقيق
قد يبدو «انتحال هوية مسؤول لفعل ذلك مؤقّتاً» أسهل على المدى القصير.
لكن على المدى الطويل، يصعب الأمر عند التدقيق، وتحقيق الأعطال، وتغيير الصلاحيّات، ومراجعات الأمان.
25. الخلاصة
رمز انتحال الهوية في Windows آليّة مهمّة لاستخدام إدارة صلاحيّات Windows بشكل صحيح.
لكنّه أيضاً مجال يسهل فيه أن تصبح الشيفرة خطيرة رغم أنّها تبدو ناجحة، إن استُخدِم دون فهم.
نورد النقاط التي يجب الإحاطة بها.
- رمز الوصول يمثّل السياق الأمنيّ من مستخدم ومجموعة وصلاحيّات وغيرها
- للعمليّة رمز أساسيّ
- رمز انتحال الهوية يُلحَق بمؤشّر الترابط أساساً، ويُستخدَم في فحص الوصول
- الانتحال ليس رفع صلاحيّات
- الرمز الأساسيّ ورمز انتحال الهوية يختلفان في الاستخدام
- يتغيّر بحسب مستوى الانتحال ما إذا كان يمكن التعريف فقط، أم الوصول الفعليّ، أم التفويض إلى جهة بعيدة
- عند الانتحال عبر Win32 API، استدعِ
RevertToSelfدائماً ضمنtry/finally - في .NET، عبّر عن نطاق صغير عبر
WindowsIdentity.RunImpersonated/RunImpersonatedAsync - عند استخدام
LogonUser، انتبه لإدارة بيانات الاعتماد ونوع تسجيل الدخول - في مشاركات الشبكة، يتعلّق الأمر ليس فقط بالانتحال، بل أيضاً بتصميم تفويض Kerberos وحساب الخدمة
- أدِر مقبض الرمز عبر
SafeAccessTokenHandleوusing - اختبر ليس حالات النجاح فقط، بل أيضاً الحالات التي ينبغي أن تُرفَض
المهمّ في تنفيذ رمز انتحال الهوية ليس نقطة «عمل بصفة مستخدم آخر» وحدها، بل جعل الحالة قابلة لشرح هذه الأمور.
أيّ معالجة
بطلب مَن
بصفة أيّ حساب
ضمن أيّ نطاق فقط تُنفَّذ
وأين تُعاد إلى وضعها الأصليّ
وكيف يُسجَّل النجاح والفشل
إن أمكن ترتيب هذا القدر، فلن يكون الانتحال آليّة مخيفة.
يصبح أداة عمليّة للاستفادة من ACL الخاصّة بـ Windows، وحساب الخدمة، وخادم الملفّات القائم، وأصول النطاق الداخليّ.
المراجع
- مجموعة نماذج الشيفرة الخاصّة بهذا المقال (مكتبة، عرض توضيحيّ، اختبارات وحدة)
https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/windows-impersonation-token - Microsoft Learn: Access Tokens
https://learn.microsoft.com/en-us/windows/win32/secauthz/access-tokens - Microsoft Learn: Impersonation Tokens
https://learn.microsoft.com/en-us/windows/win32/secauthz/impersonation-tokens - Microsoft Learn: Impersonation Levels
https://learn.microsoft.com/en-us/windows/win32/secauthz/impersonation-levels - Microsoft Learn:
SECURITY_IMPERSONATION_LEVELenumeration
https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level - Microsoft Learn:
ImpersonateLoggedOnUserfunction
https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-impersonateloggedonuser - Microsoft Learn:
RevertToSelffunction
https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-reverttoself - Microsoft Learn:
LogonUserfunction
https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera - Microsoft Learn:
DuplicateTokenExfunction
https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex - Microsoft Learn:
TOKEN_INFORMATION_CLASSenumeration
https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-token_information_class - Microsoft Learn:
WindowsIdentity.RunImpersonated
https://learn.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.runimpersonated - Microsoft Learn:
WindowsIdentity.RunImpersonatedAsync
https://learn.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.runimpersonatedasync - Microsoft Learn: Configure Windows Authentication in ASP.NET Core
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
ما ينبغي فعله قبل التخلّص من حاسوب Windows ── قائمة تحقّق عمليّة لمحو البيانات وفكّ ربط الحسابات والنسخ الاحتياطيّ
نستعرض ما ينبغي فعله قبل التخلّص من حاسوب Windows أو تسليمه أو بيعه أو إعادته بموجب عقد إيجار، من زاوية النسخ الاحتياطيّ، ومحو البيانات، ...
التعهيد الخارجي والتطوير التعاقدي لتطبيقات Windows: ما ينبغي تنظيمه قبل الطلب
قبل تكليف جهة خارجية بتطوير تطبيق Windows تعاقديّاً، إليك النقاط التي ينبغي تنظيمها: تعديل البرمجيّات القائمة، وتكامل الأجهزة، وCOM/Activ...
دمج مصادقة Entra ID في تطبيقات WinForms/WPF ── التشكيل العمليّ لـ MSAL.NET ووسيط WAM
نُنظّم خطوات دمج مصادقة Entra ID في تطبيقات WinForms/WPF لسطح المكتب. نشرح مفهوم العميل العامّ، وتسجيل التطبيق، وAcquireTokenSilent في MS...
التاريخ والوقت والمناطق الزمنية في تطبيقات الأعمال ── من فخاخ DateTime إلى مبدأ التخزين بتوقيت UTC وتصميم الاختبارات
ينزاح الوقت 9 ساعات بعد نقل الخادم ── نرتّب أسباب أعطال التاريخ والوقت انطلاقًا من خاصيّة Kind في DateTime والتحويل الضمنيّ. نشرح التمييز...
استخدام SQLite في تطبيقات C# للأعمال ── وضع WAL، والتحكّم الحصريّ، والوقاية من التلف، والتمييز عن EF Core
نرتّب هنا المعرفة العمليّة اللازمة لدمج SQLite في تطبيقات الأعمال عبر Microsoft.Data.Sqlite. نشرح وضع WAL، وSQLITE_BUSY وتوحيد مسار الكتا...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- ما هو رمز انتحال الهوية في Windows؟
- رمز انتحال الهوية هو رمز يُلحَق بمؤشّر ترابط (thread) ليخضع لفحص الوصول ضمن سياق أمنيّ مختلف. الأمر لا يعني أنّ العمليّة بأكملها تصبح مستخدماً آخر، بل إنّ مؤشّر الترابط الذي يُنتحَل فيه وحده هو الذي يخضع لفحص الوصول إلى الملفّات والسجلّ (Registry) وغيرها بصلاحيّات ذلك المستخدم. الأمر ليس «سحراً يجعلك مسؤولاً»، أي ليس رفع صلاحيّات، بل هو آليّة تجعل جزءاً محدوداً من معالجة عمليّة الخادم يخضع لفحص الوصول بصلاحيّات مستخدم العميل.
- ما الفرق بين الرمز الأساسيّ (Primary Token) ورمز انتحال الهوية (Impersonation Token)؟
- يمثّل الرمز الأساسيّ السياق الأمنيّ للعمليّة، ويُستخدَم في إطلاق العمليّات مثل CreateProcessAsUser. أمّا رمز انتحال الهوية فيُستخدَم ليعمل مؤشّر الترابط في سياق أمنيّ مختلف، ويظهر في ImpersonateLoggedOnUser وانتحال العميل في Named Pipe. عند الرغبة في إطلاق عمليّة، يلزم من حيث المبدأ رمز أساسيّ، وإن كان المتوفّر رمز انتحال هوية فقط، فالمسار هو إنشاء رمز أساسيّ عبر DuplicateTokenEx. الخلط بين الاثنين يوقعك في حيرة أمام أخطاء مثل Access denied.
- لماذا يظهر Access denied رغم أنّي أنتحل الهوية؟
- توجد عدّة زوايا ينبغي التحقّق منها. تحقّق داخل نطاق الانتحال ليس فقط من Name عبر WindowsIdentity.GetCurrent()، بل أيضاً من ImpersonationLevel، وتأكّد أنّه أعلى من مستوى Identification لا يقتصر عليه. تحقّق من قائمة التحكّم بالوصول (ACL) الخاصّة بالمورد المستهدَف، ومن أنّ عمليّة الإدخال/الإخراج الفعليّة لا تعمل خارج نطاق الانتحال أو في مهمّة أخرى، ومن أنّ الأمر ليس مشكلة القفزة المزدوجة (double hop) التي تنجح فيها الوصولات المحليّة وتفشل فقط عبر UNC، ومن تأثير رمز غير مرفوع (non-elevated) بسبب UAC. قد يكون الاسم كما هو متوقَّع لكن الصلاحيّات غير كافية.
- كيف أنفّذ الانتحال بأمان في .NET؟
- الأساس هو استخدام WindowsIdentity.RunImpersonated / RunImpersonatedAsync وحصر نطاق الانتحال داخل تعبير لامدا. أيّ معالجة غير متزامنة تحتاج إلى الانتحال يجب أن تُنتظَر (await) داخل RunImpersonatedAsync، وتجنَّب إطلاق مهمّة من نوع fire-and-forget من داخل نطاق الانتحال. عند الانتحال عبر واجهات Win32 API، استدعِ RevertToSelf دائماً ضمن try/finally. اجعل نطاق الانتحال محصوراً في عمليّات الإدخال/الإخراج الضروريّة فقط، وأدِر مقابض الرموز (token handles) عبر SafeAccessTokenHandle وusing.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.
روابط عامة