System.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.
OpenSSL SSL_connect: Connection was reset in connection to github.com:443
git config --global http.sslBackend schannel
[http] sslBackend = schannel
دایرکتوری فونتهای فارسی
@font-face { font-family: name; src: url("path-to-the-font-file/name.otf") format("opentype"); } @font-face { font-family: name; font-weight: bold; src: url("path-to-the-font-file/name.otf") format("opentype"); } @font-face { font-family: 'name'; font-style: normal; font-weight: normal; src: url('http://example.com/name.ttf') format('truetype'); }
EF Code First #12
- کلاس واحد کار رو استاتیک تعریف کرده. این مورد در یک برنامه asp.net یعنی به اشتراک گذاری واحد کار جاری با تمام کاربران سایت.
- از StructureMap استفاده کرده اما چون درک درستی از تزریق وابستگیها نداشته از الگوی service locator آن (ObjectFactory.GetInstance) برای وهله سازی استفاده کرده (از این مورد فقط در حالتهای ناچاری مانند تهیه یک role provider سفارشی که وهله سازی آن در کنترل ما نیست و راسا مدیریت میشود باید استفاده کرد)
- از StructureMap استفاده کرده اما نمیدونسته که این کتابخانه خودش میتونه در پایان درخواستهای وب اشیاء مورد استفاده رو dispose کنه و کار اضافی انجام داده.
و ....
از امروز قابلیتهای اجتماعی گوگل ریدر با گوگل پلاس یکی شده و به همین جهت یک سری از امکانات قدیمی آن حذف گردیدهاند؛ مانند به اشتراک گذاری و لایک زدن و غیره و تمام اینها با دکمهی به علاوه یک گوگل پلاس جایگزین شدهاند. اینبار میشود علاقمندیها را از گوگل ریدر به حلقههای گوگل پلاس هدایت کرد. همهی اینها خوب؛ اما سیستم به اشتراک گذاریهای روزانهی من رو به هم ریخته این کارها! قبلا از حاصل اشتراکها در گوگل ریدر، یک فید تهیه میشد که الان دیگر وجود خارجی ندارد. هیچکدام از حلقههای گوگل پلاس هم فید ندارند. به این ترتیب این محصول، تبدیل به یک فیدخوان معمولی شده است. مثل 100ها برنامهی مشابه دیگر.
اما ... در ادامه ببینیم که چطور میتوان از گوگل ریدر جدید، راهی به خارج باز کرد!
الف) زمانیکه در محیط گوگل ریدر، بر روی دکمه به علاوه یک گوگل پلاس قرار گرفته در ذیل یک مطلب کلیک میکنید، میتوان نام حلقهای از حلقههای گوگل پلاس را انتخاب کرد تا مطلب ما به آن ارسال شود. یکی از این حلقهها، Public نام دارد. بنابراین اینبار اگر قصد به اشتراک گذاری عمومی مطلبی را داشتید مطابق شکل زیر، حلقه Public را وارد نمائید و جالب اینجا است که این حلقه فقط در گوگل ریدر قابل انتخاب است؛ نه در خود گوگل پلاس.
ب) اکنون به اکانت گوگل پلاس خود مراجعه کنید. در قسمت پروفایل، به لینک بالای صفحه دقت نمائید. چیزی است شبیه به:
https://plus.google.com/u/0/userId/posts
این userId برای ادامه کار مهم است.
ج) از این userId برای فراخوانی لینک زیر استفاده خواهیم کرد (userId آن باید جایگزین شود):
https://plus.google.com/_/stream/getactivities/?&sp=[1,2,"userId",null,null,null,null,"social.google.com",[]]
خروجی آن طبق معمول متداول گوگل، فرمت JSON است و میتوان از آن لیست اشتراکهای عمومی را استخراج کرد. برای نمونه کلاس GooglePlusJsonParser تهیه شده زیر میتواند این عملیات را انجام دهد:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
//needs a ref. to System.Web.Extensions asm.
using System.Web.Script.Serialization;
namespace GooglePlus
{
[DebuggerDisplay("{DateTime}-{Title}-{Comment}-{Url}")]
public class GooglePlusItem
{
public DateTime DateTime { set; get; }
public string Url { set; get; }
public string Title { set; get; }
public string Comment { set; get; }
}
public static class Utils
{
public static DateTime ConvertFromUnixTimestamp(this long data)
{
return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(data);
}
public static string ToSafeString(this object data)
{
return data == null ? string.Empty : data.ToString();
}
}
public class GooglePlusJsonParser
{
public static IList<GooglePlusItem> GetMyPublicItemsList(string userId)
{
var url = string.Format("https://plus.google.com/_/stream/getactivities/?&sp=[1,2,\"{0}\",null,null,null,null,\"social.google.com\",[]]", userId);
var jsonData = new WebClient().DownloadString(url);
return ParsePublicData(jsonData);
}
public static IList<GooglePlusItem> ParsePublicData(string jsonData)
{
jsonData = removeRedundantData(jsonData);
var posts = parseJson(jsonData);
return createItemsList(posts);
}
private static IList<GooglePlusItem> createItemsList(object[] posts)
{
var result = new List<GooglePlusItem>();
foreach (object[] post in posts)
{
var items = (object[])((object[])post[66])[0];
result.Add(new GooglePlusItem
{
DateTime = (((Int64)post[5]) / 1000).ConvertFromUnixTimestamp(),
Title = items[3].ToSafeString(),
Url = items[1].ToSafeString(),
Comment = post[4].ToSafeString()
});
}
return result;
}
private static object[] parseJson(string jsonData)
{
var jsonObject = new JavaScriptSerializer().Deserialize<object[]>(jsonData);
var posts = (object[])(((object[])jsonObject[1])[0]);
return posts;
}
private static string removeRedundantData(string jsonData)
{
return jsonData.Replace(")]}'", string.Empty)
.Replace("[,", "[\"\",")
.Replace(",,", ",\"\",")
.Replace(",,", ",\"\",");
}
}
}
نحوه استفاده از آن هم میتواند فراخوانی متد GooglePlusJsonParser.GetMyPublicItemsList به همراه userId یاده شده باشد.
ایده اصلی از:
Getting the Google+ Feed for any profile in JSON
اگر به ساز و کار شیرپوینت مایکروسافت دقت کنید، همه چیز را داخل دیتابیس ذخیره میکند (از اطلاعات رکوردها گرفته تا فایلها و غیره). حال شاید این سؤال مطرح شود که برای ذخیره سازی فایلهایی با تعداد بیش از یک میلیون عدد، استفاده از دیتابیس مناسب است یا فایل سیستم متداول. برای پاسخ به این سؤال باید به نکات ذیل توجه داشت:
- هر نوع عملیاتی که بر روی فایلها صورت گیرد، بستن، بازکردن و غیره، نیازمند اعمالی در سطح سیستم عامل است (برای مثال بررسی سطح دسترسی لازم برای انجام اینکارها).
- هر گونه عملیاتی بر روی فایلها نیازمند یک حداقل قفل گذاری بر روی آنها است که این نیز مصرف CPU قابل توجهی را سبب خواهد شد.
- تمامی اعمال ذکر شده کل سرور و تمامی سرویسهای در حال اجرا را تحت تاثیر قرار داده و بازدهی آنهارا کاهش میدهند.
- حتی سیستم عاملها نیز از یک file system database جهت مدیریت اعمال خود استفاده میکنند اما این روش برای مدیریت میلیونها و میلیاردها فایل بهینه سازی نشده است.
- ذخیره سازی میلیونها و میلیاردها فایل به تدریج سبب ایجاد fragmentation قابل توجهی شده و این مورد نیز بر روی کارآیی تاثیر منفی خواهد گذاشت (همچنین این مورد بر روی طول عمر تجهیزات ذخیره سازی دادهها تاثیر منفی دارند).
- تهیه پشتیبان و بازگرداندن میلیونها فایل بسیار زمانگیر است (برای مثال جابجایی یک فایل یک مگابایتی بسیار سریعتر است از جابجایی 100 فایل 10 کیلوبایتی).
- مدیریت تغییرات و همچنین بررسی اینکه چه شخصی چه فایلی را قرار داده، حذف کرده یا تغییر داده است در حالت استفاده از file system مشکل است.
- به صورت پیش فرض عموما مباحث replication و امثال آن توسط روش استفاده از file system خصوصا با تعداد بالای فایل، پشتیبانی نمیشود.
- در حالت استفاده از file system ، برنامههای وب باید دسترسی write بر روی یک سری پوشه داشته باشند که این مورد همیشه از دیدگاه امنیتی مساله ساز بوده و مشکل آفرین.
- کرش file system مساوی است با کرش سیستم عامل و بازگشت اینها زمانبر خواهد بود.
با توجه به این نکات استفاده از دیتابیس برای ذخیره سازی تعداد زیادی فایل، مزایای زیر را به همراه خواهد داشت:
- اکثر سیستمهای دیتابیسی امروزی برای کار با حجم عظیمی از دادهها به حد بلوغ خود رسیدهاند.
- هنگام استفاده از دیتابیس برای ذخیره سازی فایلها دیگر سر و کار ما با میلیونها فایل نخواهد بود و حداکثر چند فایل دیتابیس و ملحقات آن مانند لاگ فایل، کل سیستم را تشکیل میدهند.
- فایلهای دیتابیس برای مثال SQL Server ، همیشه توسط SQL Server در حالت باز قرار داشته و مباحث قفلگذاری بر روی فایلهای دیتابیس و بررسی سطح دسترسی و غیره توسط سیستم عامل در اینجا به حداقل خود میرسد.
- در این حالت بار سیستم عامل شما تنها سیستمی است که مشغول سرویس دهی اطلاعات دیتابیسهای شما است.
- جستجوی فایلها، حتی جستجو در محتوای این فایلهای ذخیره شده در یک دیتابیس بسیار سریعتر از روش file system میباشد. امکان استفاده از کوئریهای SQL انعطاف پذیری خاصی را به این سیستمها خواهند داد (برای مثال قابلیت full text search مربوط به SQL server امکان جستجو بر روی رکوردهایی با محتوای pdf را نیز پس از انجام اندکی تنظیمات، دارا میباشد).
- هنگام کار با دیتابیس مباحث تراکنشی نقش بسیار حائز اهمیتی را بازی میکنند اما عموما سیستم عاملها در این زمینه نیازمند کار و برنامه نویسی قابل توجهی هستند (این قابلیت به ویندوز ویستا اضافه شده است).
- کرش یک دیتابیس عموما سبب کرش سیستم عامل یا حتی کرش سایر دیتابیسهای موجود نخواهد شد.
- امکان تهیه پشتیبان از دیتابیسها و بازیابی آنها ساده است. (حداقل از بازیابی میلیونها فایل سادهتر است)
- امکانات replication به صورت پیش فرض در اکثر سیستمهای دیتابیسی امروزی مهیا است.
- امکان ثبت وقایع و مدیریت اطلاعات افزوده شده به دیتابیس، از طریق نرم افزارهایی که برای این کار نوشته خواهند شد (یا حتی امکانات توکار این برنامهها) از هر لحاظ نسبت به روش file system برتری دارد.
- امکانات سوئیچ کردن به دیتابیسی دیگر در شبکه در صورت کرش یک نود، مهیا است و پیش بینی شده است.
- برای استفاده از یک دیتابیس توسط یک برنامه وب، نیازی به داشتن دسترسی write بر روی هیچ فولدری وجود ندارد که این خود یک مزیت امنیتی مهم است و همچنین امکان محدود کردن سطوح دسترسی به فایلهای ذخیره شده در دیتابیس با برنامههای نوشته شده نیز سادهتر است. (البته در اینجا مسلما منظور از دیتابیس، دیتابیس Access نیست و SQL Server یا MySQL مد نظر هستند)
لیست ها و آرایه ها در #F
#1 let emptyList = [] #2 let oneItem = "one " :: [] #3 let twoItem = "one " :: "two " :: []
#2 تعریف یک لیست به همراه یک آیتم
#3 تعریف یک لیست به همراه دو آیتم
قبول دارم که دستورالعمل بالا برای مقدار دهی اولیه به لیست کمی طولانی و سخت است. برای همین میتونید از روش زیر هم استفاده کنید.
let shortHand = ["apples "; "pears"]
میتونید از عملگر @ برای پیوستن دو لیست به هم نیز استفاده کنید.
let twoLists = ["one, "; "two, "] @ ["buckle "; "my "; "shoe "]
نکته : تمام آیتمهای موجود در لیست باید از یک نوع باشند. بعنی امکان تعریف لیستی که دارای آیتم هایی با datatypeهای متفاوت باشد باعث تولید خطای کامپایلری میشود. اما اگر نیاز به لیستی دارید که باید چند datatype رو هم پوشش دهد میتونید از objectها استفاده کنید.
let objList = [box 1; box 2.0; box "three"]
در هنگام استفاده از عملگرها @ و :: مقدار لیست تغییر نمیکند بلکه یک لیست جدید تولید خواهد شد.
#1 let one = ["one "] #2 let two = "two " :: one #3 let three = "three " :: two #4 let rightWayRound = List.rev three #5 let main() = printfn "%A" one printfn "%A" two printfn "%A" three printfn "%A" rightWayRound
#2 تعریف لیستی که دارای دو آیتم است(آیتم دوم لیست خود از نوع لیست است)
#3 تعریف لیستی که دارای سه آیتم است(ایتم دوم لیست خود از نوع لیستی است که دارای دو آیتم است)
# از تابع List.rev برای معکوس کردن آیتمهای لیست three استفاده کردیم و مقادیر در لیستی به نام rightWayRound قرار گرفت.
#5 تابع main برای چاپ اطلاعات لیست ها
بعد از اجرا خروجی زیر مشاهده میشود.
["one "] ["two "; "one "] ["three "; "two "; "one "] ["one "; "two "; "three "]
F#List | Net Array | Net List | |
#1 امکان تغییر در عناصر لیست | No | Yes | Yes |
#2 امکان اضافه کردن عنصر جدید | No | No | Yes |
#3 جستجو | On | O1 | O1 |
#2 در #F بعد از ساختن یک لیست دیگه نمیتونید یک عنصر جدید به لیست اضافه کنید.
#3 جستجوی در لیستهای #F به نسبت لیستها و آرایههای در دات نت کندتر عمل میکند.
استفاده از عبارات در لیست ها
برای تعریف محدوده در لیست میتونیم به راحتی از روش زیر استفاده کنیم
let rangeList = [1..99]
let dynamicList = [for x in 1..99 -> x*x]
for(int x=0;x<99 ; x++) { myList.Add(x*x); }
روش عادی برای کار با لیستها در #F استفاده از الگوی Matching و توابع بازگشتی است.
let listOfList = [[2; 3; 5]; [7; 11; 13]; [17; 19; 23; 29]] let rec concatList l = match l with | head :: tail -> head @ (concatList tail) | [] -> [] let primes = concatList listOfList printfn "%A" primes
خروجی :
[2; 3; 5; 7; 11; 13; 17; 19; 23; 29]
در جدول زیر تعدادی از توابع ماژول لیست رو مشاهده میکنید.
نام تابع | توضیحات |
List.length | تابعی که طول لیست را برمی گرداند |
List.head | تابعی برای برگشت عنصر اول لیست |
List.tail | تمام عناصر لیست را بر میگرداند به جز عنصر اول |
List.init | یک لیست با توجه به تعداد آیتم ایجاد میکند و یم تابع را بر روی تک تک عناصر لیست ایجاد میکند. |
List.append | یک لیست را به عنوان ورودی دریافت میکند و به لیست مورد نظر اضافه میکند و مجموع دو لیست را برگشت میدهد |
List.filter | فقط عناصری را برگشت میدهد که شرط مورد نظر بر روی آنها مقدار true را برگشت دهد |
List.map | یک تابع مورد نظر را بر روی تک تک عناصر لیست اجرا میکند و لیست جدید را برگشت میدهد |
List.iter | یک تابع مورد نظر را بر روی تک تک عناصر لیست اجرا میکند |
List.zip | مقادیر دو لیست را با هم تجمیع میکند و لیست جدید را برگشت میدهد. اگر طول 2 لیست ورودی یکی نباشد خطا رخ خواهدداد |
List.unzip | درست برعکس تابع بالا عمل میکند |
List.toArray | لیست را تبدیل به آرایه میکند |
List.ofArray | آرایه را تبدیل به لیست میکند |
List.head [5; 4; 3] List.tail [5; 4; 3] List.map (fun x -> x*x) [1; 2; 3] List.filter (fun x -> x % 3 = 0) [2; 3; 5; 7; 9]
Sequence Collection
seq در #F یک توالی از عناصری است که هم نوع باشند. عموما از sequenceها زمانی استفاده میکنیم که یک مجموعه از دادهها با تعداد زیاد و مرتب شده داشته باشیم ولی نیاز به استفاده از تمام عناصر آن نیست. کارایی sequence در مجموعههای با تعداد زیاد از listها به مراتب بهتر است. sequenceها را با تابع seq میشناسند که معادل IEnumerable در دات نت است. بنابر این هر مجمو عه ای که IEnumerable رو در دات نت پیاده سازی کرده باشد در #F با seq قابل استفاده است.مثال هایی از نحوه استفاده seq
#1 seq بامحدوده 1 تا 100 و توالی 10
seq { 0 .. 10 .. 100 }
seq { for i in 1 .. 10 do yield i * i }
seq { for i in 1 .. 10 -> i * i }
let isprime n = let rec check i = i > n/2 || (n % i <> 0 && check (i + 1)) check 2 let aSequence = seq { for n in 1..100 do if isprime n then yield n }
در این بخش به ارائه مثال هایی کاربردیتر از چگونگی استفاده از seq در #F میپردازیم. برای شروع نحوه ساخت یک seq خالی یا empty رو خواهم گفت.
let seqEmpty = Seq.empty
let seqOne = Seq.singleton 10
let seqFirst5MultiplesOf10 = Seq.init 5 (fun n -> n * 10) Seq.iter (fun elem -> printf "%d " elem) seqFirst5MultiplesOf10
0 10 20 30 40
let seqFromArray2 = [| 1 .. 10 |] |> Seq.ofArray
let seqFromArray1 = [| 1 .. 10 |] :> seq<int>
let containsNumber number seq1 = Seq.exists (fun elem -> elem = number) seq1 let seq0to3 = seq {0 .. 3} printfn "For sequence %A, contains zero is %b" seq0to3 (containsNumber 0 seq0to3)
برای جستجو و پیدا کردن یک آیتم در seq میتونیم از seq.find استفاده کنیم.
let isDivisibleBy number elem = elem % number = 0 let result = Seq.find (isDivisibleBy 5) [ 1 .. 100 ] printfn "%d " result
استفاده از lambda expression در توابع
lamdaExpressoion از تواناییها مورد علاقه برنامه نویسان دات نت است و کمتر کسی است حاضر به استفاده از آن در کوئریهای linq نباشد. در #F نیز میتوانید از lambda Expression استفاده کنید. در ادامه به بررسی مثال هایی از این دست خواهیم پرداخت.
تابع skipWhile
همانند skipWhile در linq عمل میکند. یعنی یک predicate مورد نظر را بر روی تک تک عناصر یک لیست اجرا میکند و آیتم هایی که شرط برای آنها true باشد نادیده گرفته میشوند و مابقی آیتمها برگشت داده میشوند.
let mySeq = seq { for i in 1 .. 10 -> i*i }
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
let mySeqSkipWhileLessThan10 = Seq.skipWhile (fun elem -> elem < 10) mySeq
mySeqSkipWhileLessThan10 |> printSeq
خروجی به صورت زیر است:
16 25 36 49 64 81 100
مثال:
let mySeq = seq { for i in 1 .. 10 -> i*i } let truncatedSeq = Seq.truncate 5 mySeq let takenSeq = Seq.take 5 mySeq let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
#1 truncatedSeq |> printSeq #3 takenSeq |> printSeq
1 4 9 16 25 //truncate 1 4 9 16 25 //take
Tuples
tuples در #F به گروهی از مقادیر بی نام ولی مرتب شده که میتوانند انواع متفاوت هم داشته باشند گفته میشود. ساختار کلی آن به صورت ( element , ... , element ) است که هر element خود میتواند یک عبارت نیز باشد.(مشابه کلاس Tuple در #C که به صورت generic استفاده میکنیم)
// Tuple of two integers. ( 1, 2 ) // Triple of strings. ( "one", "two", "three" ) // Tuple of unknown types. ( a, b ) // Tuple that has mixed types. ( "one", 1, 2.0 ) // Tuple of integer expressions. ( a + 1, b + 1)
#1 میتونیم از الگوی Matching برای دسترسی به عناصر tuple استفاده کنیم.
let print tuple1 = match tuple1 with | (a, b) -> printfn "Pair %A %A" a b
let (a, b) = (1, 2)
let c = fst (1, 2) // return 1 let d = snd (1, 2)// return 2
let third (_, _, c) = c
زمانی که یک تابع باید بیش از یک مقدار را بازگشت دهد از tupleها استفاده میکنیم. برای مثال
let divRem a b = let x = a / b let y = a % b (x, y)
آسیب پذیری 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)
اکنون ورودی 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
2) اسکنر اکانتیکس
چگونه از 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
5) میتوان از تنظیمات IIS یا وب سرورهای دیگر برای جلوگیری از SQLi استفاده نمود.
6) استفاده از چند کاربرِ دیتابیس در برنامه و بکارگیری سطح دسترسی محدود و مناسب( ^ , ^ ).
7) از ORM استفاده کنید و اگر نیاز به سرعت بیشتری دارید از یک Micro ORM استفاده کنید؛ با در نظر داشتن نکات لازم.
Jon P. Smith, author of Entity Framework Core in Action, explains what a multi-tenant app is and then digs into the things you need to do to make a multi-tenant app using ASP.NET Core with EF Core.
00:00 Countdown
02:19 Introduction and Community Links
18:25 What are multi-tenant web applications?
21:14 Single level multi-tenant demo
29:00 Partitioning tenants with EF Core QueryFilter
38:00 Admin features: creating users and tenants
43:00 Q&A
43:00 Hierarchical multi-tenant
59:00 How to get started
1:06:30 Database sharding and connection string management
1:16:30 Scaling with Azure SQL Elastic Pools
1:19:00 Conclusion