امنیت برنامههای اندرویدی: ضرورتها و تهدیدها

با رشد روزافزون فناوری و گسترش استفاده از گوشیهای هوشمند، برنامههای موبایل به بخش جداییناپذیری از زندگی روزمره انسانها تبدیل شدهاند. از پرداخت قبضها گرفته تا مدیریت حسابهای بانکی، خرید آنلاین، و حتی برقراری ارتباط، برنامههای موبایل نقش مهمی در تسهیل این فرآیندها ایفا میکنند.
با این حال، افزایش استفاده از این برنامهها باعث شده که خطرات امنیتی نیز به طرز قابل توجهی افزایش یابد. در این مقاله، ضرورتهای امنیت برنامههای موبایل و تهدیدهایی که متوجه این برنامهها به ویژه در حوزههای حساس بانکی، مالی و استارتاپی است، بررسی میشود.
ضرورت امنیت برنامههای اندرویدی
- حفاظت از اطلاعات حساس کاربران برنامههای موبایل معمولاً با دادههای حساس کاربران، از جمله اطلاعات شخصی، رمزهای عبور، و اطلاعات بانکی سروکار دارند. اگر این دادهها به درستی محافظت نشوند، ممکن است در معرض خطر سرقت یا سوءاستفاده قرار گیرند. کاربران به طور طبیعی انتظار دارند که برنامههایی که استفاده میکنند، حریم خصوصی و امنیت دادههای آنها را تضمین کنند.
- اعتماد کاربران اعتماد کاربران یکی از کلیدیترین عوامل موفقیت یک برنامه است. اگر برنامهای از لحاظ امنیتی ضعف داشته باشد، احتمال از دست رفتن اعتماد کاربران بسیار زیاد است. این مسئله میتواند تأثیرات جدی بر اعتبار شرکتها یا استارتآپها داشته باشد.
- مقابله با تهدیدهای جدید همزمان با پیشرفت فناوری، حملات سایبری نیز پیچیدهتر و متنوعتر شدهاند. امنیت برنامههای موبایل باید به طور مداوم با این تهدیدات بهروز شود تا از نفوذهای احتمالی جلوگیری شود.
- آسیبپذیریها در برنامههای موبایل
- ذخیرهسازی دادههای ناامن
یکی از رایجترین آسیبپذیریها در برنامههای موبایل، ذخیرهسازی دادهها در مکانهای ناامن، مانند حافظه داخلی یا خارجی دستگاه، بدون استفاده از رمزنگاری مناسب است. این مسئله میتواند به مهاجمان اجازه دهد به راحتی به دادههای حساس دسترسی پیدا کنند.
ذخیره اطلاعات در حافظه داخلی بدون رمزنگاری
یکی از نکات مهم امنیت برنامههای اندرویدی، برنامهای که اطلاعات حساسی مانند نشانی ایمیل یا توکن دسترسی را به صورت متن ساده در فایلهای حافظه داخلی ذخیره میکند.
مثال عملی:
اگر فایل به نام userData.txt در حافظه داخلی دستگاه ذخیره شود:
email=user@example.com
auth_token=12345abcde
- هر مهاجمی که به دستگاه دسترسی فیزیکی یا از طریق بدافزار دسترسی پیدا کند، میتواند این فایل را بخواند.
ذخیره دادهها در حافظه خارجی (SD Card)
- برنامهای که دادههای حساس را در کارت حافظه SD دستگاه ذخیره میکند. حافظه خارجی معمولاً برای همه برنامهها و حتی برخی کاربران قابل دسترسی است.
- مثال عملی:
برنامهای که اطلاعات حساس کاربر را در مسیر زیر ذخیره میکند:
/storage/emulated/0/MyApp/user_credentials.txt
- این فایل میتواند توسط هر برنامه دیگری روی دستگاه خوانده شود، زیرا حافظه خارجی دسترسی کنترلشدهای ندارد.
ذخیره دادهها در Shared Preferences بدون رمزنگاری
- Shared Preferences برای ذخیرهسازی تنظیمات ساده استفاده میشود، اما برخی توسعهدهندگان ممکن است اطلاعات حساس را در این مکان به صورت متن ساده ذخیره کنند.
- مثال:
SharedPreferences sharedPreferences = context.getSharedPreferences("MyApp", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("password", "mypassword123");
editor.apply();
- این دادهها به صورت متن ساده ذخیره میشوند و به راحتی با ابزارهای Root یا Debugger قابل استخراج هستند.
Cache کردن دادههای حساس بدون امنیت کافی
- برخی برنامهها دادههای حساس را در Cache ذخیره میکنند تا بارگذاری سریعتری داشته باشند، اما اگر این دادهها رمزنگاری نشده باشند، قابل سوءاستفاده هستند.
- مثال:
ذخیره یک تصویر حساس کاربر در مسیر Cache بدون محدودیت دسترسی:
/data/data/com.example.myapp/cache/user_photo.jpg
- این فایل در صورت Root شدن دستگاه یا استفاده از ابزارهای Debugging قابل دسترسی است.
استفاده از SQLite بدون رمزنگاری
- SQLite یک پایگاه داده سبکوزن است که معمولاً برای ذخیره اطلاعات در برنامهها استفاده میشود. اگر این پایگاه داده رمزنگاری نشود، محتویات آن قابل خواندن است.
- مثال:
اگر برنامهای از SQLite برای ذخیره اطلاعات کاربران استفاده کند:
CREATE TABLE Users (id INTEGER PRIMARY KEY, username TEXT, password TEXT);
و سپس دادهها را به صورت زیر وارد کند:
INSERT INTO Users (username, password) VALUES ('user1', 'password123');
- هر مهاجمی که به فایل پایگاه داده معمولاً در مسیر /data/data/com.example.myapp/databases/ دسترسی پیدا کند، میتواند این دادهها را استخراج کند.
ذخیره Session Tokens در فایلهای Log
- برخی برنامهها ممکن است برای دیباگ کردن اطلاعات Session Token را در فایلهای Log ذخیره کنند. این فایلها میتوانند توسط مهاجمین مورد سوءاستفاده قرار بگیرند.
- مثال:
Log.d("AuthToken", "User token: 12345abcde");
- این پیام Log ممکن است در فایل Log دستگاه ذخیره شود و هر کسی که به Log دسترسی داشته باشد میتواند آن را مشاهده کند.
چگونه این آسیب پذیری ها و مشکلات را برطرف کنیم؟
- استفاده از رمزنگاری قوی برای ذخیرهسازی دادههای حساس.
- ذخیره دادههای حساس در حافظه داخلی امن (Protected Internal Storage)
- جلوگیری از ذخیره اطلاعات حساس در Cache یا Log
- استفاده از پایگاه دادههای رمزنگاریشده مانند SQLCipher
- اعمال محدودیت دسترسی به فایلها (File Permissions)
- حذف دادههای حساس پس از استفاده (Data Cleanup)
- احراز هویت و مدیریت نشست ضعیف
سیستمهای احراز هویت نامناسب یا مدیریت ناکارآمد نشستها (Sessions) میتوانند مهاجمان را قادر سازند تا به حسابهای کاربری دسترسی پیدا کنند. برای مثال، استفاده از رمزهای عبور ضعیف یا ذخیرهسازی آنها به صورت متن ساده میتواند برنامه و داده ها و اطلاعات حساس را به مخاطره اندازد.
حملات مرتبط با احراز هویت و مدیریت نشست ضعیف معمولاً به دلیل طراحی نادرست فرآیند احراز هویت، مدیریت نادرست نشستها (Session)، یا ذخیرهسازی نامناسب دادهها رخ میدهند. در ادامه چند مثال و سناریو ارائه میشود:
۱. حمله بروت فورس (Brute Force)
- سیستم احراز هویت بدون محدودیت(rate limite) برای تعداد تلاشهای ورود. مهاجم با استفاده از ابزارهای اتوماتیک، رمز عبور کاربران را حدس میزند.
- راهحل: محدود کردن تعداد تلاشهای ناموفق، استفاده از تکنیک های rate limite و کپچا.
۲. نشستهای منقضی نشده (Session Fixation)
- نشست (Session) کاربر پس از خروج از سیستم منقضی نمیشود و مهاجم از توکن قدیمی سوءاستفاده میکند.
- راهحل: پایان دادن به نشست هنگام خروج کاربر.
۳. استفاده از رمزهای پیشفرض یا ضعیف
- سیستم به کاربران اجازه میدهد از رمز عبورهای پیشفرض یا ضعیف (مانند “123456”) استفاده کنند.
- راهحل: اعمال قوانین قوی برای رمز عبور.
۴. عدم استفاده از HTTPS
- ارتباطات احراز هویت از طریق HTTP ارسال میشود که به مهاجم اجازه شنود (Man-in-the-Middle) را میدهد.
- راهحل: استفاده از HTTPS برای تمام ارتباطات.
۵. مدیریت نامناسب توکن نشست (Session Token)
- توکن نشست به صورت غیرامن در URL یا لوکال استوریج ذخیره میشود و بهراحتی قابل سرقت است.
- راهحل: ذخیره توکنها در کوکیهای امن (Secure Cookies).
۶. سرقت نشست (Session Hijacking)
- مهاجم با شنود شبکه، توکن نشست کاربر را سرقت کرده و از آن برای ورود استفاده میکند.
- راهحل: استفاده از توکنهای دارای محدودیت زمانی و احراز هویت مجدد برای عملیات حساس.
۷. بازنشانی رمز عبور ناامن
- سیستم بازنشانی رمز عبور به روش ها و طراحی ناامن و بدون اعتبارسنجی دقیق یا ارسال لینکهای ناامن انجام میشود.
- راهحل: احراز هویت قوی برای بازنشانی رمز عبور.
۸. حمله Credential Stuffing
- مهاجم از ترکیب نام کاربری و رمز عبور افشا شده در سیستمهای دیگر برای ورود استفاده میکند.
- راهحل: تشخیص الگوهای غیرعادی و استفاده از احراز هویت دومرحلهای (MFA).
۹. ورود چندبار مصرف
- توکن احراز هویت به مهاجم اجازه میدهد تا به طور همزمان با کاربر اصلی وارد شود.
- راهحل: بررسی دقیق و محدود کردن نشستها به یک دستگاه یا مرورگر احرازهویت شده در هر زمان البته با توجه به بیزینس.
۱۰. زمان انقضای طولانی برای نشستها
- نشستهای طولانیمدت توکنها را مستعد سوءاستفاده میکند.
- راهحل: تنظیم زمان انقضای کوتاه و نیاز به احراز هویت مجدد.
مثال احراز هویت و مدیریت نشست ضعیف
- سناریو: حمله به نشست (Session Hijacking) در یک برنامه مالی
یک برنامه مالی محبوب به کاربران امکان میدهد تا پس از ورود به سیستم، موجودی حساب خود را بررسی کرده و تراکنشهای مالی انجام دهند. این برنامه از یک مکانیزم نشست (Session) برای مدیریت ورود کاربران استفاده میکند.
آسیب پذیری و مشکل: مدیریت ضعیف نشست
- عدم محدودیت زمانی برای نشستها: نشست کاربر هیچ زمان انقضای مشخصی ندارد و تا زمانی که کاربر به صورت دستی خارج نشود، فعال باقی میماند.
- ارسال شناسه نشست (Session ID) بدون رمزنگاری: شناسه نشست در درخواستهای HTTP به صورت متن ساده ارسال میشود.
- عدم استفاده از Secure و HttpOnly در کوکیها: کوکی حاوی شناسه نشست بدون تنظیم پرچمهای امنیتی Secure و HttpOnly است.
حمله: سرقت نشست از طریق شبکه ناامن
- فرض کنید کاربر به یک شبکه وایفای عمومی متصل میشود.
مهاجم، با استفاده از ابزارهای Sniffing مانند Wireshark، ترافیک HTTP را شنود میکند. - شناسه نشست در درخواستها به سرقت میرود.
به دلیل عدم استفاده از HTTPS، مهاجم میتواند Session ID را که در هدر درخواست ارسال میشود، مشاهده کند:
GET /account/balance HTTP/1.1
Host: www.finapp.com
Cookie: session_id=abc123xyz456
- بازسازی نشست کاربر توسط مهاجم:
مهاجم این Session ID را در مرورگر خود تنظیم میکند و به سیستم وارد میشود، گویی کاربر واقعی است.
نتیجه حمله:
- مهاجم میتواند به اطلاعات حساب مالی کاربر، تراکنشها و حتی انتقال وجه دسترسی پیدا کند.
- کاربر واقعی متوجه این حمله نمیشود، زیرا نشست او همچنان فعال است.
راهکار برای جلوگیری از چنین حملاتی
- استفاده از رمزنگاری و HTTPS تمام ارتباطات بین کاربر و سرور باید رمزنگاری شوند تا Session ID در ترافیک شبکه افشا نشود.
- تنظیم Secure و HttpOnly برای کوکیها:
- پرچم Secure تضمین میکند که کوکی تنها از طریق HTTPS ارسال شود.
- پرچم HttpOnly دسترسی جاوااسکریپت به کوکی را مسدود میکند، که از حملات XSS جلوگیری میکند.
Set-Cookie: session_id=abc123xyz456; Secure; HttpOnly; SameSite=Strict
- محدود کردن زمان انقضای نشست: نشستها باید زمان انقضای مشخصی داشته باشند. به عنوان مثال، کاربر پس از مدت زمان عدم فعالیت باید دوباره وارد سیستم شود.
- بازسازی Session ID پس از ورود به سیستم: هنگامی که کاربر وارد سیستم میشود یا اقدام حساسی انجام میدهد (مانند تغییر رمز عبور)، Session ID باید تغییر کند تا احتمال سرقت کاهش یابد. البته در برنامه های مالی پیشنهاد میشود برای کلیه عملیات حساس ازotp استفاده شود.
- فعال کردن احراز هویت چندمرحلهای (MFA): حتی اگر نشست به سرقت برود، مهاجم نمیتواند بدون تأیید هویت دوم (مانندotp پیامک یا اثر انگشت) اقدام حساسی انجام دهد.
- کنترل دسترسی بر اساس IP و Device IDمحدود کردن نشستها به آدرس IP و دستگاهی که نشست از آن شروع شده است، خطر حملات را کاهش میدهد. در برخی از برنامه ها و عملیات حساس پیشنهاد میشود از برخی مشخصه های دستگاه مانند android id , imei , GSF id , certificate, signature , advance id استفاده شود و در تراکنش ها و عملیات حساس بررسی شوند.
- نقص در ارتباطات شبکه برنامههایی که از مکانیزم های امنیتی مانند sslpin و پروتکلهای ارتباطی غیرایمن و ارسال اطلاعلت و داده های حساس به صورت واضح استفاده میکنند، در معرض حملاتی مانند حملات Man-in-the-Middle قرار دارند. این نوع حملات میتوانند باعث افشای دادههای حساس شوند.
- کد ناامن یا آسیبپذیر استفاده از کدهای ناامن مانند عدم اعتبارسنجی ورودی و یا کتابخانههای قدیمی که دارای آسیبپذیریهای شناخته شده هستند، میتواند به مهاجمان راه نفوذی به برنامه بدهد.
مثال کد ناامن یا آسیبپذیر
سناریو: آسیبپذیری تزریق SQL (SQL Injection) در یک برنامه رزرو آنلاین
- یک برنامه موبایل برای رزرو بلیط سینما طراحی شده است. کاربران میتوانند با وارد کردن شناسه کاربری و رمز عبور خود وارد سیستم شوند و سپس بلیط رزرو کنند.
مشکل: عدم اعتبارسنجی ورودی در پرسوجوهای پایگاه داده
- در این برنامه، اطلاعات کاربران در یک پایگاه داده MySQL ذخیره شده و برای بررسی اعتبار ورود، کد زیر استفاده میشود:
String query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
ResultSet result = statement.executeQuery(query);
if (result.next()) {
// Login successful
} else {
// Login failed
}
آسیبپذیری
- این کد به صورت مستقیم ورودی کاربر username و passwordرا بدون هیچگونه اعتبارسنجی یا استفاده از پارامترهای آماده (Prepared Statements) در دستور SQL قرار میدهد.
- مهاجم میتواند با وارد کردن مقادیر دستکاریشده، کد SQL دلخواه خود را اجرا کند.
حمله: تزریق SQL
- مهاجم در بخش نام کاربری مقدار زیر را وارد میکند:
' OR '1'='1
و در قسمت رمز عبور هر چیزی یا حتی هیچ چیزی وارد نمیکند. درخواست نهایی به شکل زیر تبدیل میشود:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = ''
- این پرسوجو همیشه درست خواهد بود، زیرا شرط OR ‘1’=’1′ همواره صحیح است. در نتیجه مهاجم میتواند بدون داشتن اطلاعات معتبر وارد سیستم شود.
پیامدها
- دسترسی غیرمجاز به حسابهای کاربران: مهاجم میتواند به اطلاعات خصوصی و تراکنشهای کاربران دیگر دسترسی پیدا کند.
- تخریب دادهها: مهاجم ممکن است دستوراتی مانند حذف جداول یا تغییر اطلاعات کاربران را اجرا کند.
- سرقت اطلاعات حساس: اطلاعات شخصی کاربران، مانند نام، آدرس و شماره تماس، ممکن است نشت پیدا کند.
راهکارهای پیشگیری
- 1.استفاده از پرسوجوهای آماده (Prepared Statements):
به جای الحاق مستقیم ورودیها به کد SQL، از پارامترهای آماده استفاده کنید. مثال امن:
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
ResultSet result = preparedStatement.executeQuery();
if (result.next()) {
// Login successful
} else {
// Login failed
}
- اعتبارسنجی ورودی:
- از الگوهای خاص برای اعتبارسنجی دادهها استفاده کنید مثلاً اطمینان حاصل کنید که username تنها شامل کاراکترهای مجاز باشد
- طول ورودیها را محدود کنید.
- غیره
- 3.استفاده از ORMها:
ابزارهایی مانند Hibernate یا Sequelize میتوانند بسیاری از آسیبپذیریهای مرتبط با پایگاه داده را به صورت خودکار مدیریت کنند. - 4.بروزرسانی منظم کتابخانهها و چارچوبها:
مطمئن شوید که از نسخههای بهروز و امن کتابخانهها و پایگاه داده استفاده میکنید. - 5.اجرای تست امنیتی خودکار:
- استفاده از ابزارهایی مانند OWASP ZAP یا Burp Suite برای شناسایی آسیبپذیریها.
6- استفاده از sp و استفاده از زبان و فریمورک های امن در این زمینه
مثال
- مهاجمی که از این آسیبپذیری مطلع است، ممکن است حتی پرسوجوهایی مانند زیر را اجرا کند:
username = 'admin'; DROP TABLE users; --
این دستور باعث حذف کل جدول کاربران میشود. در نتیجه، سیستم عملاً از کار میافتد و تمام دادهها از دست میروند.
لازم به ذکر است این مثال به منظور اموزش بیان شده و جلوی بسیاری از حملات مشابه گرفته میشود. با استفاده از تکنیک های پیچیده تر مانند تایمینگ اتک و حملات کنترل دسترسی و دور زدن احرازهویت و مشکلات عدم اعتبارسنجی ورودی ها حملات جدی تری طراحی می شود.
5.مهندسی معکوس و تغییرات غیرمجاز برنامههای موبایل به دلیل ذخیرهسازی روی دستگاه کاربر، در معرض خطر مهندسی معکوس قرار دارند. مهاجمان میتوانند کد برنامه را تحلیل کرده و از آن برای سوءاستفاده استفاده کنند. همچنین، برنامههای تغییر داده شده میتوانند به جای نسخه اصلی نصب شوند و اطلاعات کاربران را به سرقت ببرند.
3-مثال تغییر کدکلاینت(تمپرینگ)، گارد در کد و شناسایی روت (Root Detection)
1تمپرینگ (Code Tampering)
آسیب پذیری و مشکل: تغییر کد برنامه برای سوءاستفاده
- برنامهای که فایل APK آن به صورت ناامن منتشر شده و هیچگونه مکانیزمی برای تشخیص تغییر کد ندارد، ممکن است توسط مهاجمان بازسازی (Repackaging) شود. مهاجم میتواند کد را تغییر داده و نسخه مخربی از برنامه منتشر کند.
مثال حمله
- مهاجم با ابزارهایی مانند APKTool یا JADX کد برنامه را Decompile میکند.
- کدی که مربوط به پرداخت درونبرنامهای است، دستکاری میشود تا پرداختها بدون هزینه انجام شوند:
کد آسیب پذیر:
if (payment.isValid()) {
grantAccess();
} else {
showError();
}
تغییر یافته:
grantAccess();
- نسخه جدید برنامه منتشر میشود و مهاجم به تمام امکانات پولی دسترسی پیدا میکند.
راهکار پیشگیری
- یکپارچگی کد: از هش کردن (Hashing) برای بررسی تغییرات کد در زمان اجرا استفاده کنید:
String expectedHash = "abc123...";
String currentHash = calculateHash(getAppCode());
if (!expectedHash.equals(currentHash)) {
throw new SecurityException("Code integrity check failed");
}
- امضای دیجیتال: از امضای دیجیتال برای انتشار APK استفاده کنید و مطمئن شوید که برنامه تنها امضای معتبر را اجرا میکند.
2. گارد در کد (Code Guarding)
آسیب پذیری و مشکل: در معرض بودن کدهای حساس
- اگر کدهای حساس مانند کلیدهای API یا منطق امنیتی به راحتی در برنامه پیدا شوند، مهاجمان میتوانند آنها را استخراج کرده و سوءاستفاده کنند.
مثال حمله
- مهاجم فایل APK برنامه را با ابزارهای دیکامپایلر باز کرده و به کلیدهای API ذخیرهشده در کد دسترسی پیدا میکند:
private static final String API_KEY = "SECRET_KEY_123";
راهکار جلوگیری:
- استفاده از رمزنگاری
- Obfuscationاز ابزارهایی مانند ProGuardوdexguard یا R8 استفاده کنید تا کدهای برنامه مبهمسازی شوند و استخراج آنها سختتر شود.
- قبل از Obfuscation
public void fetchUserData() { ... }
بعد از Obfuscation
a.b.c();
- اجرای امنیت چندلایه:
- کلیدهای حساس را در سمت سرور نگه دارید و تنها توکنهای محدود به برنامه ارسال کنید.
- از جایگزینی کلیدها در زمان اجرا استفاده کنید.
- اطلاعات حساس مانند کلیدها در صورتی که باید بین کلاینت و سرور تبادل شود با پروتکل های امن این تبادل و به صورت رمز شده انجام شود.
3. شناسایی روت (Root Detection)
مشکل: دسترسی روت به مهاجم اجازه میدهد تا به فایلها و دادههای محافظتشده دسترسی پیدا کند.
مثال حمله
- یک مهاجم با دستگاهی که روت شده است، به فایلهای ذخیرهشده در حافظه داخلی برنامه دسترسی پیدا میکند.
- مهاجم توکن نشست (Session Token) ذخیرهشده در فایل زیر را استخراج میکند:
/data/data/com.example.myapp/shared_prefs/session.xml
- با استفاده از این توکن، مهاجم میتواند نشست کاربر را ربوده و به حساب او دسترسی پیدا کند.
راهکار شناسایی روت
- بررسی فایلهای مرتبط با روت:
در زمان اجرا، برنامه میتواند فایلهای مرتبط با ابزارهای روت مانند su را بررسی کند:
public boolean isDeviceRooted() {
String[] paths = { "/system/bin/su", "/system/xbin/su", "/system/app/Superuser.apk" };
for (String path : paths) {
if (new File(path).exists()) {
return true;
}
}
return false;
}
- بررسی پرچمهای امنیتی:
برخی از ROMهای کاستوم یا ابزارهای روت ممکن است فایلهای سیستم یا پرچمهای خاصی را تغییر دهند. - استفاده از کتابخانههای آماده:
کتابخانههایی مانند SafetyNet API یا RootBeer میتوانند بهطور خودکار دستگاههای روتشده را شناسایی کنند.
4مثال ترکیبی: تمپرینگ و شناسایی روت
حمله:
- مهاجم برنامه را روی دستگاهی روتشده نصب کرده و آن را دیکامپایل میکند.
کدهای شناسایی روت را پیدا کرده و غیرفعال میکند:
if (isDeviceRooted()) {
// Original code: showErrorMessage();
// Tampered code: continue as normal
}
- مهاجم برنامه را با کد جدید بستهبندی کرده و از نسخه تغییریافته استفاده میکند.
پیشگیری ترکیبی:
- از Code Integrity Check برای شناسایی تغییرات استفاده کنید.
- روشهای مختلف شناسایی روت را بهطور ترکیبی به کار ببرید مثلاً بررسی وجود فایلها، پرچمهای سیستم، و APIهای آماده.
- در صورت شناسایی تمپرینگ یا روت، برنامه را بهطور کامل قفل کند و به صورت امن کرش کند.
- این مثالها به شما کمک میکنند تا رویکردی چندلایه برای مقابله با تمپرینگ و شناسایی روت در برنامههای موبایل داشته باشید و امنیت آنها را بهبود ببخشید.
خطرات برنامههای بانکی، مالی و استارتاپی
- برنامههای بانکی و مالی برنامههای بانکی و مالی هدف اصلی مهاجمان هستند، زیرا مستقیماً با پول و اطلاعات حساس مالی کاربران سروکار دارند. حملاتی مانند سرقت اعتبار (Credential Theft)، فیشینگ (Phishing)، و بدافزارهای موبایلی میتوانند خسارات جدی به کاربران و مؤسسات مالی وارد کنند.
- استارتاپها و برنامههای نوآورانه استارتاپها معمولاً منابع محدودی دارند و ممکن است نتوانند روی امنیت به اندازه کافی سرمایهگذاری کنند. این موضوع آنها را به هدف آسانتری برای حملات تبدیل میکند. افشای اطلاعات کاربران یا سرقت دادههای حساس میتواند به اعتبار و رشد استارتاپها آسیب جدی وارد کند.
- تهدید به حریم خصوصی کاربران برنامههایی که دادههای کاربران را بدون رضایت آنها جمعآوری یا ذخیره میکنند، در معرض خطر نقض حریم خصوصی قرار دارند. این مسئله میتواند منجر به شکایتهای قانونی و جریمههای سنگین شود.
راهکارهای افزایش امنیت برنامههای موبایل
- رمزنگاری دادهها اطلاعات حساس باید با استفاده از الگوریتمهای رمزنگاری قوی رمزنگاری شوند. همچنین، کلیدهای رمزنگاری نباید داخل کد برنامه ذخیره شوند.
- احراز هویت چند مرحلهای (MFA) استفاده از احراز هویت چند مرحلهای میتواند امنیت دسترسی به حسابهای کاربری را افزایش دهد.
- بهروزرسانیهای منظم برنامهها باید به طور مرتب بهروزرسانی شوند تا آسیبپذیریهای جدید رفع شوند. استفاده از ابزارهای تحلیل کد میتواند در شناسایی آسیبپذیریها کمک کند.
- آموزش کاربران کاربران باید از خطرات امنیتی مانند فیشینگ و نصب برنامههای نامطمئن آگاه شوند و روشهای محافظت از اطلاعات خود را بیاموزند.
- استفاده از ابزارهای امنیتی پیشرفته ابزارهایی مانند فایروالها، سیستمهای تشخیص نفوذ، و ابزارهای تحلیل رفتار میتوانند امنیت برنامهها را افزایش دهند.
نتیجهگیری امنیت برنامههای موبایل دیگر یک انتخاب نیست، بلکه یک ضرورت است. با توجه به حساسیت دادهها و تهدیدهای موجود، توسعهدهندگان و شرکتها باید به طور جدی به امنیت برنامههای خود توجه کنند. این کار نه تنها از کاربران محافظت میکند، بلکه اعتماد آنها را نیز افزایش میدهد و باعث رشد و موفقیت پایدار برنامهها در بازار رقابتی میشود.
مطالب تکمیلی:
۱. مثال ناامن (اشتباه):
- برنامهنویس برای دیباگ کردن، توکن نشست (Session Token) را در لاگ چاپ میکند:
fun onLoginSuccess(response: LoginResponse) {
val sessionToken = response.token
val sharedPref = getSharedPreferences("auth", Context.MODE_PRIVATE)
sharedPref.edit().putString("session_token", sessionToken).apply()
Log.d("Auth", "Session Token: $sessionToken")
}
خطر:
- لاگهای برنامه (Logcat) حتی در نسخه Production قابل مشاهده هستند.
- مهاجمان با دسترسی فیزیکی به دستگاه یا ابزارهایی مثل ADB میتوانند این توکن را بدست آورند و جعل هویت کنند.
۲. مثال ایمن (راه حل درست):
- الف) عدم ثبت توکن در لاگ:
fun onLoginSuccess(response: LoginResponse) {
val sessionToken = response.token
val sharedPref = getSharedPreferences("auth", Context.MODE_PRIVATE)
sharedPref.edit().putString("session_token", sessionToken).apply()
Log.d("Auth", "User logged in successfully")
}
- ب) حذف لاگهای حساس در Production:
استفاده از کتابخانههایی مثل Timber که در حالت Release لاگها را غیرفعال میکنند:
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
} else {
Timber
{
بهترین روشهای تکمیلی:
- ذخیره توکن در مکان امن:
به جایSharedPreferences
معمولی، از EncryptedSharedPreferences استفاده کنید:
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPref = EncryptedSharedPreferences.create(
context,
"auth_encrypted",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences
- ارتباط امن با سرور:
مطمئن شوید توکن از طریق HTTPS و با احراز هویت مناسب (مثل رمزگذاری TLS) منتقل میشود.
۴. خطرات مشابه:
- استثناها (Exceptions):
try {
} catch (e: Exception) {
Log.e("API", "Error: ${e.message}")
}
catch (e: Exception) {
Log.e("API", "Request failed")
}
نتیجه:
- هرگز دادههای حساس (توکن، رمز عبور، اطلاعات شخصی) را در لاگها ثبت نکنید.
- از ابزارهای مانیتورینگ امنیتی (مثل Firebase Crashlytics) برای ردیابی خطاها بدون افشای اطلاعات حساس استفاده کنید.
روشهای ترکیبی شناسایی روت (Root Detection) در اندروید
- برای شناسایی دستگاههای روت شده، باید از روشهای چندلایه (Combined Checks) استفاده کنید تا احتمال دور زدن تشخیص توسط مهاجمان کاهش یابد. در زیر، روشهای پیشرفته و مثالهای کد ارائه شده است:
۱. بررسی وجود فایلها و دایرکتوریهای مرتبط با روت
- فایلهای خاصی مانند
su
،Superuser.apk
، یا دایرکتوری/system/bin
ممکن است در دستگاههای روت شده وجود داشته باشند. - مثال در کاتلین:
fun isRootedByFiles(): Boolean {
val paths = arrayOf(
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/system/sd/xbin/su",
"/system/bin/failsafe/su",
"/data/local/su",
"/su/bin/su"
)
return paths.any { File(it).exists() }
}
- نکات:
- مهاجمان ممکن است مسیر
su
را تغییر دهند یا فایل را مخفی کنند. - این روش به تنهایی کافی نیست.
۲. بررسی Build Tags و System Properties
- پارامترهای سیستمی مانند
ro.build.tags
یاro.debuggable
ممکن است در دستگاههای روت شده تغییر کنند. - مثال در جاوا:
public boolean isRootedByBuildTags() {
String buildTags = android.os.Build.TAGS;
return buildTags != null && buildTags.contains("test-keys");
}
public boolean isDebuggable() {
return (android.os.Build.class.getField("DEBUG").getBoolean(null) ||
(android.os.Build.class.getField("DEBUG_SYSTEM_APP").getBoolean(null));
}
- توضیح:
test-keys
نشاندهنده استفاده از بیلد غیررسمی (مثلاً با دسترسی روت) است.DEBUG
فعال ممکن است نشانه دسترسی روت باشد.
۳. بررسی دسترسی SU با اجرای دستور
- اجرای دستور
su
و بررسی خروجی آن یک روش کلاسیک است. - مثال در کاتلین:
fun isRootedBySuCommand(): Boolean {
return try {
Runtime.getRuntime().exec("su").exitValue() == 0
} catch (e: IOException) {
false
}
}
- خطر:
- برخی روتها مانند Magisk ممکن است دسترسی
su
را از برنامههای غیرمجاز مخفی کنند.
۴. بررسی SafetyNet API
- استفاده از Google SafetyNet Attestation API برای شناسایی دستگاههای روت شده یا آلوده.
- مثال در کاتلین:
fun checkSafetyNet() {
val safetyNetClient = SafetyNet.getClient(context)
val nonce = generateNonce() // تولید Nonce منحصر به فرد
safetyNetClient.attest(nonce, API_KEY)
.addOnSuccessListener { response ->
val attestation = response.jwsResult.split(".")[1]
val data = String(Base64.decode(attestation, Base64.DEFAULT))
val json = JSONObject(data)
val isRooted = json.optBoolean("ctsProfileMatch", false).not()
if (isRooted) {
}
}
.addOnFailureListener { e ->
}
}
- نکات:
- نیاز به API Key و اتصال به اینترنت دارد.
- ممکن است در برخی دستگاههای چینی (بدون سرویسهای گوگل) کار نکند.
۵. بررسی Magisk (رایجترین روت مدرن)
- Magisk ممکن است فایلهای خاصی مانند
magisk
یاmagiskhide
را در سیستم ایجاد کند. - مثال در کاتلین:
fun isMagiskInstalled(): Boolean {
val process = Runtime.getRuntime().exec("ps -A")
val output = process.inputStream.bufferedReader().readText()
return output.contains("magisk") || output.contains("magiskd")
}
- روش جایگزین:
بررسی وجود پکیج Magisk Manager:
fun isMagiskManagerInstalled(): Boolean {
val pm = context.packageManager
return try {
pm.getPackageInfo("com.topjohnwu.magisk", 0)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
. بررسی Mount
- در دستگاههای روت شده، پارتیشن
/system
اغلب با حالت Read-Write مونت شده است. - مثال در جاوا:
public boolean isSystemReadWrite() {
try {
Process process = Runtime.getRuntime().exec("mount");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("/system") && line.contains("rw")) {
return true;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
استفاده از کتابخانههای پیشرفته (مثل RootBeer)
- کتابخانههایی مانند RootBeer چندین روش تشخیص روت را ترکیب میکنند.
- مثال در کاتلین:
val rootBeer = RootBeer(context)
if (rootBeer.isRooted) {
}
- مزیت:
- شامل دهها چک متفاوت (فایلها، دستورات، سیستم پراپرتیها) است.
- بهروزرسانی مداوم برای مقابله با روشهای جدید روت.
۸. بررسی Hook Frameworkها (مثل Xposed یا Frida)
- فریمورکهایی مانند Xposed یا Frida برای تزریق کد به برنامه استفاده میشوند.
- مثال تشخیص Xposed:
fun isXposedInstalled(): Boolean {
return try {
Class.forName("de.robv.android.xposed.XposedBridge")
true
} catch (e: ClassNotFoundException) {
false
}
}
- مثال تشخیص Frida:
بررسی پورت پیشفرض Frida (مثلاً27042
):
fun isFridaRunning(): Boolean {
return try {
val socket = Socket()
socket.connect(InetSocketAddress("127.0.0.1", 27042), 1000)
socket.close()
true
} catch (e: IOException) {
false
}
}
. بررسی سطح دسترسی برنامه
- در دستگاههای روت شده، برخی برنامهها مجوزهای غیرعادی (مثل
android.permission.ACCESS_SUPERUSER
) دارند. - مثال در جاوا:
public boolean hasSuperuserPermission() {
return checkPermission("android.permission.ACCESS_SUPERUSER",
android.os.Process.myPid(),
android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED;
}
جمعآوری گزارش امنیتی
- گزارش امنیتی باید شامل تمامی روشهای فوق و نتایج آنها باشد. مثال ساختار گزارش:
{
"root_detection": {
"su_files": true,
"test_keys": false,
"safetynet_cts_match": false,
"magisk_installed": true,
"xposed_detected": false,
"system_rw": true
},
"risk_level": "HIGH",
"recommendation": "Block access to sensitive features."
}
نتیجهگیری و بهترین روشها
- ترکیب حداقل ۵ روش مختلف برای کاهش احتمال دور زدن.
- رمزنگاری و مبهمسازی (Obfuscation) کد تشخیص روت برای جلوگیری از مهندسی معکوس.
- عدم اتکا به SafetyNet به تنهایی (به دلیل امکان Spoof شدن).
- بررسی دورهای در طول اجرای برنامه (نه فقط در زمان راهاندازی).
- استفاده از سرور-ساید چک برای تایید سلامت دستگاه (مثلاً با استفاده از امضای دیوایس).
fun isDeviceRooted(): Boolean {
return isRootedByFiles() ||
isRootedBySuCommand() ||
isMagiskInstalled() ||
!isSafetyNetPassed() ||
isSystemReadWrite()
}
دیدگاهتان را بنویسید