مطالب
تحلیل و بررسی ده روش آسیب پذیری نرم افزار بر اساس متدولوژی OWASP - قسمت اول SQL Injection
در این سری از مقالات، ده روش برتر آسیب پذیری نرم افزار بر اساس متدولوژی OWASP مورد بررسی قرار میگیرد. یادگیری این روش‌ها منحصر به زبان برنامه نویسی خاصی نیست و رعایت این نکات برای برنامه نویسانی که قصد نوشتن کدی امن دارند، توصیه میشود. کلمه‌ی OWASP مخفف عبارت Open Web Application Security Protocol Project می‌باشد. در واقع OWASP یک متدولوژی و پروژه‌ی متن باز است که معیارهایی را برای ایمن سازی نرم افزار مورد بررسی قرار میدهد.



آسیب پذیری SQL Injection یا به اختصار SQLi

تزریق SQL، یکی از قدیمی ترین، شایع‌ترین و مخرب‌ترین آسیب پذیری‌ها، برای برنامه‌ها می‌باشد و در صورت برقراری شرایط مناسب جهت حمله و با اعمال نفوذ، از طریق تزریق SQL ، مهاجم میتواند با دور زدن فرآیندهای اعتبارسنجی و احراز هویت یک برنامه، به تمامی محتوای پایگاه داده‌ی آن و گاها کنترل سرور، دسترسی پیدا کند. این حمله برای افزودن، ویرایش و حذف رکوردهای یک پایگاه داده مبتنی بر SQL انجام میشود.


عملکرد SQL Injection

برای اجرای SQLهای مخرب در برنامه‌هایی که از پایگاه‌های داده‌ی مبتنی بر SQL مانند (SQL Server ،MySQL ،PostgreSQL ،Oracle و ...) استفاده میکنند، هکر یا مهاجم در اولین گام باید به دنبال ورودی‌هایی در برنامه باشد که درون یک درخواست SQL قرار گرفته باشند (مانند صفحات لاگین، ثبت نام، جستجو و ...).

کد زیر را در نظر بگیرید:

# Define POST variables
uname = request.POST['username']
passwd = request.POST['password']

# SQL query vulnerable to SQLi
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"

# Execute the SQL statement
database.execute(sql)
در این کد، موارد امنیتی برای جلوگیری از تزریق SQL تعبیه نشده‌است و هکر با اندکی دستکاری پارامترهای ارسالی میتواند نتایج دلخواهی را برای نفوذ، بدست بیاورد. قسمتی از کد که دستور SQL را اجرا میکند و پارامترهای ورودی از کاربر را در خود جای میدهد، بصورت نا امنی کدنویسی شده‌است.

اکنون ورودی password را برای نفوذ، تست میکنیم. مهاجم بدون داشتن نام کاربری، قصد دور زدن احراز هویت را دارد. بجای password عبارت زیر را قرار میدهد:

password' OR 1=1  

در نهایت در بانک اطلاعاتی دستور زیر اجرا میشود:

 SELECT id FROM users WHERE username='username' AND password=  'password' OR 1=1'

میدانیم که 1=1 است. پس بدون در نظر گرفتن اینکه شما برای username و password چه چیزی را وارد نمودید، عبارت درست در نظر گرفته میشود:

شرط اول   and   شرط دوم =
نتیجه  or 1=1
چون 1=1 است 
همیشه شرط کوئری درست خواهد بود


معمولا در بانک اطلاعاتی، اولین کاربری که وارد میکنند Administrator برنامه می‌باشد. پس به احتمال قوی شما میتوانید با مجوز ادمین به برنامه وارد شوید. البته میتوان با دانستن تنها نام کاربری هم به‌راحتی با گذاشتن در قسمت username بدون دانستن password، به برنامه وارد شد؛ زیرا میتوان شرط چک کردن password را کامنت نمود:

-- MySQL, MSSQL, Oracle, PostgreSQL, SQLite
' OR '1'='1' --
' OR '1'='1' /*
-- MySQL
' OR '1'='1' #
-- Access (using null characters)
' OR '1'='1' %00
' OR '1'='1' %16




ابزارهایی برای تست آسیب پذیری SQLi

1) برنامه‌ی DNTProfiler

2) اسکنر اکانتیکس

3) sqlmap
4) روش دستی که بهترین نتیجه را دارد و نیاز به تخصص دارد.


چگونه از SQL Injection جلوگیری کنیم

1) روی داده‌هایی که از کاربر دریافت میگردد، اعتبار سنجی سمت کلاینت و سرور انجام شود. اگر فقط به اعتبارسنجی سمت کلاینت اکتفا کنید، هکر به‌راحتی با استفاده از  پروکسی، داده‌ها را تغییر می‌دهد. ورودی‌ها را فیلتر و پاکسازی و با لیست سفید یا سیاه بررسی کنید ( ^^^^ ).

2) از کوئری‌هایی که بدون استفاده از پارامتر از کاربر ورودی گرفته و درون یک درخواستSQL قرار میگیرند، اجتناب کنید:

[HttpGet]
        [Route("nonsensitive")]
        public string GetNonSensitiveDataById()
        {
            using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
            {
                connection.Open();
                SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Id = {Request.Query["id"]}", connection);
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        string returnString = string.Empty;
                        returnString += $"Name : {reader["Name"]}. ";
                        returnString += $"Description : {reader["Description"]}";
                        return returnString;
                    }
                    else
                    {
                        return string.Empty;
                    }
                }
            }
        }


با استفاده از پارامتر: (بهتر است نوع دیتا تایپ پارامتر و طول آن ذکر شود)

[HttpGet]
        [Route("nonsensitivewithparam")]
        public string GetNonSensitiveDataByNameWithParam()
        {
            using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
            {
                connection.Open();
                SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Name = @name", connection);
                command.Parameters.AddWithValue("@name", Request.Query["name"].ToString());
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        string returnString = string.Empty;
                        returnString += $"Name : {reader["Name"]}. ";
                        returnString += $"Description : {reader["Description"]}";
                        return returnString;
                    }
                    else
                    {
                        return string.Empty;
                    }
                }
            }
        }


3) از Stored Procedureها استفاده کنید و بصورت پارامتری داده‌های مورد نیاز را به آن‌ها پاس دهید: (بهتر است نوع دیتا تایپ پارامتر و طول آن ذکر شود)  

 [HttpGet]
        [Route("nonsensitivewithsp")]
        public string GetNonSensitiveDataByNameWithSP()
        {
            using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
            {
                connection.Open();
                SqlCommand command = new SqlCommand("SP_GetNonSensitiveDataByName", connection);
                command.CommandType = System.Data.CommandType.StoredProcedure;
                command.Parameters.AddWithValue("@name", Request.Query["name"].ToString());
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        string returnString = string.Empty;
                        returnString += $"Name : {reader["Name"]}. ";
                        returnString += $"Description : {reader["Description"]}";
                        return returnString;
                    }
                    else
                    {
                        return string.Empty;
                    }
                }
            }
        }


4) اگر از داینامیک کوئری استفاده میکنید، داده‌های مورد استفاده‌ی در کوئری را بصورت پارامتری ارسال کنید:

فرض کنید چنین جدولی دارید

CREATE TABLE tbl_Product
(
Name NVARCHAR(50),
Qty INT,
Price FLOAT
)

GO

INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Shampoo', 200, 10.0);
INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Hair Clay', 400, 20.0);
INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Hair Tonic', 300, 30.0);

یک پروسیجر را دارید که عملیات جستجو را انجام میدهد و از داینامیک کوئری استفاده میکند.

ALTER PROCEDURE sp_GetProduct(@Name NVARCHAR(50))
AS
BEGIN
    DECLARE @sqlcmd NVARCHAR(MAX);
    SET @sqlcmd = N'SELECT * FROM tbl_Product WHERE Name = ''' + @Name + '''';
    EXECUTE(@sqlcmd)
END

با اینکه از Stored Procedure استفاده میکنید، باز هم در معرض خطر SQLi می‌باشید. فرض کنید هکر چنین درخواستی را ارسال میکند:

Shampoo'; DROP TABLE tbl_Product; --

نتیجه، تبدیل به دستور زیر میشود:

SELECT * FROM tbl_Product WHERE Name = 'Shampoo'; DROP TABLE tbl_Product; --'

برای جلوگیری از SQLi در کوئریهای داینامیک SP بشکل زیر عمل میکنیم:

ALTER PROCEDURE sp_GetProduct(@Name NVARCHAR(50))
AS
BEGIN
    DECLARE @sqlcmd NVARCHAR(MAX);
    DECLARE @params NVARCHAR(MAX);
    SET @sqlcmd = N'SELECT * FROM tbl_Product WHERE Name = @Name';
    SET @params = N'@Name NVARCHAR(50)';
    EXECUTE sp_executesql @sqlcmd, @params, @Name;
END
در اینجا در پارامتر params@ نوع دیتاتایپ و طول آن را مشخص میکنید و کارکتر quote به صورت خودکار حذف میشود و از SQLi جلوگیری میکند. 


5) میتوان از تنظیمات IIS یا وب سرورهای دیگر برای جلوگیری از SQLi استفاده نمود.

6) استفاده از چند کاربرِ دیتابیس در برنامه و بکارگیری سطح دسترسی محدود و مناسب( ^ , ^ ).

7) از  ORM استفاده کنید و اگر نیاز به سرعت بیشتری دارید از یک Micro ORM استفاده کنید؛ با در نظر داشتن نکات لازم  

مطالب
لینک‌های هفته‌ی اول بهمن

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

Visual Studio


ASP. Net


طراحی و توسعه وب


اس‌کیوال سرور


سی شارپ


عمومی دات نت


ویندوز


مسایل اجتماعی و انسانی برنامه نویسی


متفرقه

مطالب
بلاگ‌ها و مطالب مطالعه شده در هفته قبل (هفته سوم آبان)

وبلاگ‌ها و سایت‌های ایرانی


ASP. Net


طراحی وب


به روز رسانی‌ها


ابزارها


سی‌شارپ


عمومی دات نت


دلفی



ویندوز


متفرقه

  • کدام سایت‌ها مطالب شما را کپی کرده‌اند؟! (البته شبیه به این کار را با Google alerts هم می‌شود انجام داد. فقط کافی است آدرس سایت خودتان را در گوگل alert اضافه کنید. هر جایی لینکی به شما داده شود یا امثال آن، یک ایمیل آنی یا روزانه بسته به تنظیمات برای شما ارسال خواهد کرد.)


مطالب
در مورد ادامه ...

در حال تهیه یک سیستم blogging هستم که بشود آن‌را چند کاربره یا چند نویسنده‌ای کرد. این سیستم جدید تا یک ماه دیگر جایگزین سیستم وصله پینه‌ای جاری خواهد شد (متن یکجا، css لینک شده از یک سایت دیگر، syntax highlighting از یک سایت دیگر، عکس‌ها در یک سرور مجزا، کامنت‌ها کلا در یک سرور دیگر (دیسکاس) بجز هاست بلاگر، لینک‌های روزانه در یک سایت ثالث، فایل‌های مثال‌ها در سروری دیگر! هاست اصلی بلاگ (بلاگر) هم کلا در ایران فیلتر است!). البته همین سیستم وصله پینه‌ای 4 سال است که داره کار می‌کنه!
به همین دلیل برای پیدا کردن وقت جهت بازنویسی سیستم، این سایت تا یک ماه دیگر در حالت تعلیق خواهد بود.
در این بین اگر علاقمند به مشارکت در بلاگ جدید (با همین آدرس با همین عنوان) به عنوان نویسنده هستید، یک ایمیل به من ارسال کنید (vahid_nasiri در یاهو). موضوع سایت مشخص است. اهداف آن هم به همین ترتیب. فقط فنی است. تنها مطالب فنی قرار است ارسال شود. پس از آماده شدن سایت جدید، یک اکانت نویسنده مطلب برای شما ارسال خواهد شد. با تشکر از همکاری شما.

مطالب
مفاهیم پایه سیستم های کنترل نسخه؛ قسمت سوم : جمع بندی
در اولین قسمت این سری، گیت و در قسمت دوم ، SVN را بررسی کردیم؛ در این مقاله قصد داریم یک جمع بندی از این دو مقاله داشته باشیم.
احتمالا در مورد این دو سیستم حرف‌های زیادی شنیده‌اید و احتمالا بیشتر آن‌ها در مورد گیت نظر مساعدتری داشته‌اند؛ ولی تفاوت‌هایی بین این دو سیستم هست که باید به نسبت هدف و نیازی که دارید آن را مشخص کنید. یکی از اصلی‌ترین این تفاوت‌ها این است که svn یک سیستم مرکزی است؛ ولی گیت اینگونه نیست که در ادامه تفاوت این دو مورد را تشریح می‌کنیم.
یک. SVN یک مخزن مرکزی دارد که همه‌ی تغییراتی که روی کپی‌ها انجام می‌شود، باید به سمت مخزن مرکزی Commit یا ارسال شوند. ولی در سیستم گیت یک سیستم مرکزی وجود ندارد و هر مخزنی که fork یا Clone می‌شود، یک مخزن جداگانه به حساب می‌آید و Commit شدن تنها به مخزن کپی شده صورت میگیرد و در صورت pull request ادغام با مخزن اولیه خودش صورت میگیرد.
دو. گیت به نسبت svn از پیچیدگی بیشتری برخوردار است؛ ولی برای پروژه‌های بزرگتر که کاربران زیادی با آن کار می‌کنند و احتمال شاخه بندی‌های زیادتر، در آن وجود دارد بهتر عمل می‌کند. موقعی که یک پروژه یا تیم کوچکی روی آن کار می‌کنند به دلیل commit شدن مستقیمی که svn دارد، کار راحت‌تر و آسان‌تر صورت می‌گیرد ولی با زیاد شدن کاربران و حجم کار، گیت کارآیی بالاتری دارد.
سه. از آن جا که گیت نیاز به fork شدن دارد و یک مخزن کاملا مجزا از پروژه اصلی تولید می‌کند؛ سرعت بهتری نسبت به svn که یک کپی از زیر مجموعه ساختار اصلی ایجاد می‌کند دارد.
چهار. شاخه بندی یک مفهوم اصلی و مهم در گیت به شمار می‌آید که اکثر کاربران همه روزه از آن استفاده می‌کنند و این اجازه را می‌دهد که که تغییرات و تاریخچه فعالیت هر کاربر را بر روی هر شاخه، جداگانه ببینیم. در svn پیاده سازی شاخه‌ها یا تگ‌ها سخت و مشکل است. همچنین شاخه بندی کار در svn به شکل سابق با کپی کردن صورت گرفته که گاهی اوقات به دلایلی که در قسمت قبل گفتیم، باعث ناسازگاری می‌گردد.
پنج. حجم مخازن گیت به نسبت svn خیلی کمتر است برای نمونه پروژه موزیلا 30 درصد حجم کمتری در مخزن گیت دارد. یکی از دلایلی که svn حجم بیشتری میگیرد این است که به ازای هر فایل دو فایل موجود است یکی که همان فایل اصلی است که کاربر با آن کار می‌کند و دیگری یک فایل دیگر در شاخه svn. است که برای کمک به عملیاتی چون وضعیت، تفاوت ها، ثبت تغییرات به کار می‌رود. در صورتی که در آن سمت، گیت، تنها به یک فایل شاخص 100 بایتی برای هر  دایرکتوری کاری نیاز دارد
شش. گیت عملیات کاربری را به جز fetch و push، خیلی سریع انجام میدهد. این عملیات شامل یافتن تفاوت‌ها، نمایش تاریخچه، ثبت تغییرات، ادغام شاخه‌ها و جابجایی بین شاخه‌ها می‌گردد.
هفت. در سیستم SVN به دلیل ساختار درختی که دارد، می‌توانید زیر مجموعه‌ی یک مخزن را بررسی کنید ولی در سیستم گیت اینکار امکان پذیر نیست. البته باید به این نکته توجه داشت که برای یک پروژه‌ی بزرگ شما مجبور هستید همیشه کل مخزن را دانلود کنید. حتی اگر تنها نسخه‌ی خاصی از این زیرمجموعه را در نظر داشته باشید. به همین علت در شهرهایی که اینترنت گرانقیمت و یا سرعت پایین عرضه می‌شود، گیت به صرفه‌تر است و زمان کمتری برای دانلود آن می‌ برد.
موارد تعریف شده زیر طبق گفته ویکی سایت Kernel.Org ذکر می‌شود:
  • گیت از سیستم SVN سریعتر عمل می‌کند.
  • در سیستم گیت هر شاخه بندی کل تاریخچه خود را به دنبال دارد.
  • فایل git که تنظیمات مخزن داخلش قرار دارد، ساختار ساده‌ای دارد و به راحتی می‌توان در صورت ایجاد مشکل، آن را حل کرد و به ندرت هم پیش می‌آید که مشکلی برایش پیش بیاید.
  • پشتیبانی گیری از یک سیستم مرکزی مثل SVN راحت‌تر از پشتیبانی گیری از پوشه‌های توزیع شده در مخزن گیت است.
  • ابزارهای کاربری svn تا به الان پیشرفت‌های چشمگیری داشته است. پلاگین‌ها و برنامه‌های بیشتری نسبت به سیستم گیت دارد. یکی از معروفترین این پلاگین‌ها، ابزار  tortoisesvn  است (البته ابزارهای گیت امروز رشد چشمگیرتری داشته اند که در قسمت اول نمونه‌های آن ذکر شد).
  • سیستم svn برای نسخه بندی و تشخیص تفاوت‌ها از یک سیستم ساده اعداد ترتیبی استفاده می‌کند که اولین ثبت با شماره یک آغاز شده و به ترتیب ادامه می‌یابد و برای کاربران هم خواندنش راحت است و هم قابل پیش بینی است. به همین جهت برای بررسی تاریخچه‌ها و دیگر گزارش‌ها تا حدی راحت عمل می‌کند. در سیستم شاخه بندی این سیستم شماره گذاری چندان مطلوب نیست و متوجه نمی‌شوید که این شاخه از کجا نشات گرفته است. در حال حاضر برای پروژه‌ی موزیلا این عدد به 6 رقم رسیده است ولی در آن سمت، سیستم گیت از هش SH-1 استفاده می‌کند که یک رشته 40 کاراکتری است و 8 رقم اول آن به منشاء اشاره می‌کند که باعث می‌شود متوجه بشویم که این شاخه از کجا آمده است ولی از آنجا که این عدد یکتا ترتیبی نیست، برای خواندن و گزارشگیری‌هایی که در SVN راحت صورت می‌گیرد، در گیت ممکن نیست یا مشکل است.
  • گیت رویدادهای ادغام و شاخه بندی را بهتر انجام می‌دهد.

مطالب
خلاصه اشتراک‌های روز جمعه 1390/07/01
پاسخ به بازخورد‌های پروژه‌ها
عدم authorization بر اساس Permissions
Admin، User و ... این‌ها یکسری اسم بیشتر نیستند و به خودی خود ارزشی ندارند. اگر کاربری نقش CanAccessToSystemMaintenance در لیست نقش‌هاش ثبت شده باشه، به اون دسترسی پیدا می‌کنه. اگر نه، خیر. این شما هستید که به این نام‌ها مفهوم می‌بخشید (اگر در جایی استفاده شوند یا خیر). به همین جهت فیلتر Authorize هم یک لیست از نقش‌ها را قبول می‌کند. هم فیلتر Authorize و هم متد IsInRole بر اساس User Claims کار می‌کنند (یعنی لیست نقش‌های کاربر از بانک اطلاعاتی خوانده شده و به کوکی او در زمان لاگین اضافه می‌شوند. اگر این لیست فقط یک نقش را دارد (نامش برای این سیستم مهم نیست)، آن شخص فقط به همان یک نقش دسترسی خواهد داشت).
اگر می‌خواهید متد User.IsInRole به فیلد Permissions شما واکنش نشان دهد، باید لیست مربوطه، به لیست نقش‌ها یا Claims آن کاربر اضافه شود. در غیراینصورت کار نمی‌کند ( فیلتر Authorize هم به همین صورت).
- Mvc5AuthorizeAttribute.cs دقیقا همان فیلتر Authorize اصلی است که برای واکنش نشان دادن به اطلاعات AJax ایی طراحی شده و اصلا اطلاعات Permissions را بررسی نمی‌کند.
- فیلتر Mvc5ClaimAccessAuthorizeAttribute.cs به اطلاعات Claim خاصی واکنش نشان می‌دهد.
مطالب
لینک‌های هفته سوم آذر

وبلاگ‌ها و سایت‌های ایرانی


Visual Studio



ASP. Net


طراحی وب


اس‌کیوال سرور



Nhibernate


عمومی دات نت


ویندوز


متفرقه