<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های Hassan Mohammadi</title><link>https://virgool.ir/feed/@HassanMmdi</link><description>A simple software student</description><language>fa</language><pubDate>2026-11-12 10:09:28</pubDate><image><url>https://files.virgool.ir/upload/users/455964/avatar/AvoQbd.jpeg</url><title>Hassan Mohammadi</title><link>https://virgool.ir/@HassanMmdi</link></image><item><title>ساخت JWT جعلی با سوء استفاده از JKU</title><link>https://virgool.ir/@HassanMmdi/hacking-jwt-tokens-jku-claim-misuse-amwwobsbpqya</link><description>بعضی وقتا توی هدر توکن های JWT پارامتری به نام JKU وجود داره، این پارامتر یک URL هست که به مجموعه ای از کلید های عمومی اشاره داره و سرور برای تایید توکن از این کلید های عمومی استفاده میکنه، توی این پست ما درمورد سوء استفاده از این پارامتر حرف میزنیم و توضیح میدیم که چطور یه توکن جعلی با  مشخصات دلخواه تولید کنیم.قبله اینکه ادامه بدیم اگه درمورد JWT اطلاعات زیادی ندارید ما یه سری توضیحات مختصر دادیم ولی بهتره خودتون در موردش بیشتر مطالعه کنید.Resources for studyJSON Web TokenIntroduction to JSON Web Tokensتوکن JWT چیه ؟توکن JWT یا JSON Web Token یک رشته رمزنگاری شده هست که برای احراز هویت کاربران استفاده میشه،هر کاربر بعد از لاگین کردن، یه توکن برای اون تولید میشه. این توکن مختص به هر کاربر هست و یکتاست. هر زمان که کاربر درخواست دسترسی به منبعی (resource) رو میده، به همراه درخواست، توکن خودشو تو  header  میزاره (یا میتونه تو کوکی های مرورگر ذخیره شده باشد) و می‌فرسته . سرور با decode کردن توکن می‌تونه بفهمه که کاربر مجوز دسترسی به یک منبع را دارده یا خیر.  ساختار احراز هویت کاربریک توکن JWT از سه بخش تشکیل شده:Headerهدر معمولا از دو بخش تشکیل شده نوع توکن و الگوریتم رمزنگاری شده.2. Payloadقسمت payload حاوی اطلاعات مورد نیاز هست که به آن‌ها claim هم می‍‌گن.3. Signatureامضای توکن برای بررسی صحت توکن استفاده میشه.یک نمونه توکن JWT  و دیتا های هر بخشJKU تو قسمت قبلی ما با JWT اشنا شدیم در این قسمت به پارامتر JKU میپردازیم، همون طور که در بخش اول توضیح دادیم JKU (JWK Set URL) یک URI هست که به مجموعه ای از کلیدهای عمومی encode شده با JSON اشاره میکنه که یکی از آنها مربوط به کلید مورد استفاده برای امضای دیجیتالی JWS یا JSON Web Signature هست.خوب، شما می تونید تو ? اینجا ? بیشتر در مورد JWS بخونید چون خارج از محدوده این پست هست.scenarioمن تو یه lab که برای این مشکل طراحی شده است ثبت نام کردم، بعد از ثبت نام، سایت یک توکن در اختار من قرار داد، توکن های JWT به شکل زیر هستن:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImprdSI6Imh0dHA6Ly93aXRyYXAuY29tOjgwMDAvandrcy5qc29uIn0.eyJpYXQiOjE1NzQyNjk0NTksInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNTc0MzU1ODU5fQ.Kx3O-EV_w5jLteNLbOkT0qih0m52vnn1JJniRCVPB8jP0S-UdnhET8f0kO_q88DyyXYzTUosusQ4a5D87ZhoXM5kQSokN-FKUok1oMQaa4fPydq4jEb9ReTO6pJv3OsK17JiGEAWpWcE9fYbQAT59sUGfG1qKvZssiJtHTUljzy4B8y1fBOkik4L_rYJc5fD-d4lLAhAY6 — kt_CiD0gv3OmI4IyHNiPnbKIHhe_YbCsU7VgOaA3vDCxDJ8SXc5ctM2PeUqfwwq18Fz_8z6syuwDAj_dslKp7bwMzKVKhJcwINUPvLF1-FOzdG3bFrwLq3JZ59whgIMR2NU_2mhwVAبا استفاده از سایت jwt.io می تونیم هدر و بخش های مختلف توکن رو مشاهده کنیم:الگوریتم مورد استفاده برای امضای توکن &#34;RS256&#34; هست.این توکن از پارامتر هدر JKU استفاده میکنه که حاوی URL مجموعه JSON Web Key هست که برای تأیید توکن استفاده میشه.اگر فایل jwks.json را مشاهده کنیم:curl http://witrap.com:8000/jwks.jsonدیتای موجود در jwksاگر مهاجم یک جفت کلید عمومی-خصوصی ایجاد کنه و با استفاده از کلید خصوصی تولید شده یک توکن جعلی ایجاد کنه و مقدار پارامتر &#34;jku&#34; را با URI  جدید جایگزین کنه، در اصل توکن جعلی توسط سرور پذیرفته میشه.برای ساخت یک توکن جعلی می تونیم مراحل زیر رو طی کنیممرحله اول : ایجاد یک جفت کلید عمومی-خصوصیبرای ایجاد کلید عمومی- خصوصی از دستور های زیر استفاده می کنیم :openssl genrsa -out keypair.pem 2048&#xA;openssl rsa -in keypair.pem -pubout -out publickey.crt&#xA;openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem - &#xA;out pkcs8.keyخروجی دستور های بالاPrivate Key: pkcs8.keyPublic Key: publickey.crtبرای ساخت توکن از سایت jwt.io استفاده می کنیم :کلید عمومی (publickey.crt) و کلید خصوصی (pkcs8.key) رو تو  مکان های مربوطه به خود در بخش &#34;VERIFY SIGNATURE&#34; قرار میدیم.  قرار دادن کلید عمومی - خصوصی در صورت درست قراردادن کلید ها قسمت Signature Verified به رنگ ابی درمیاد در صورت اینکه کلید ها مشکلی داشته باشه و معتبر نباشه هشدار میده.نقش یا role رو روی &#34;admin&#34; تنظیم می کنیم، یعنی ما میخوایم به سطح دسترسی ادمین برسیم و یه جورایی داریم توکن اونو جعل می کنیمتغییر role به adminدر اینجا می تونیم ادرس JKU رو تغییر بدیم که روند این کار رو توی قسمت بعدی توضیح میدیم. من چون با VPN به این lab متصل هستم پس یه جورایی با lab توی یه شبکه هستیم اگه بخوایم این سناریو رو توی دنیا واقعی پیاده کنیم باید محلی برای اپلود jwks.json داشته باشیم ، در واقع این سناریو که دارم توضیحش میدم یه جورایی توی محیط لوکال هست.بزارید فایل jwks.json رو بگیریم ببینیم چه محتوایی داره:wget http://witrap.com:8000/jwks.jsonاطلاعات فایل jwks.jsonخوب همون طور که مشاهده می کنید دو فیلد n و e داره برای اینکه بتونیم کلید عمومی که توی قسمت بالا تولید کردیم رو به این فرمت تبدیل کنیم میتونیم از این اسکریپت پایتونی استفاده کنیم:from Crypto.PublicKey import RSA&#xA; fp = open(&#34;publickey.crt&#34;, &#34;r&#34;)&#xA; key = RSA.importKey(fp.read())&#xA; fp.close()&#xA; print(&#34;n:&#34;, hex(key.n))&#xA; print(&#34;e:&#34;, hex(key.e))اسکریپت بالا رو میتونیم با دستور زیر اجرا کنیم:python3 ./getPublicParams.py خروجی دستور:python3 ./getPublicParams.py                                                        n:0xb9e1e6b260bacff52a8a2b709d262e0a5632b152daf87cc20c18bb57d7f4cb491fe5a8e0719bbd9ff8a642ec2121eaae822d406f81c0d530fd71b0e66d99613a5b935feb76ee5c3fb9c7e5dfead9061e23150696901edb80bdf66c320d2883b0d27c742ff7030b2d50df9e5b2f4a280c22de5671a007bc37c164fc3424a0d653882c136ff85fdcd7a7c5e8cb5ffbce5609d7ea57e777350634c2eed4de9cdd900367e7198bafe1fe328f6dae2a7a5537adf0cfb01a273f1db040d67ac02520f920283d99cfb066ae143d9f7343e1e4ad0ba7c68e6d422e39cd0a18e5ebb80f34491c39d3c4f25be75cd64604d1ed7ea08dec2e2b8b00eaafd5f068c9eb48400d&#xA;e: 0x10001طبق ساختار jwks.json قبلی که بار گیری کردیم کلید ها n و e تولید شده را جایگذاری می کنیم و دوباره سیو می کنیم، jwks نهایی به این شکله:cat ./jwks.json  {&#xA;    &#34;keys&#34;: [&#xA;        {&#xA;            &#34;kty&#34;: &#34;RSA&#34;,&#xA;            &#34;use&#34;: &#34;sig&#34;,&#xA;            &#34;kid&#34;: &#34;324-23234324-544535-1320214&#34;,&#xA;            &#34;alg&#34;: &#34;RS256&#34;,&#xA;            &#34;n&#34;: &#34;b9e1e6b260bacff52a8a2b709d262e0a5632b152daf87cc20c18bb57d7f4cb491fe5a8e0719bbd9ff8a642ec2121eaae822d406f81c0d530fd71b0e66d99613a5b935feb76ee5c3fb9c7e5dfead9061e23150696901edb80bdf66c320d2883b0d27c742ff7030b2d50df9e5b2f4a280c22de5671a007bc37c164fc3424a0d653882c136ff85fdcd7a7c5e8cb5ffbce5609d7ea57e777350634c2eed4de9cdd900367e7198bafe1fe328f6dae2a7a5537adf0cfb01a273f1db040d67ac02520f920283d99cfb066ae143d9f7343e1e4ad0ba7c68e6d422e39cd0a18e5ebb80f34491c39d3c4f25be75cd64604d1ed7ea08dec2e2b8b00eaafd5f068c9eb48400d&#34;,&#xA;            &#34;e&#34;: &#34;10001&#34;&#xA;        }&#xA;    ]&#xA;}       حالا باید یک سرور HTTP راه اندازی کنیم برای این کار از پایتون استفاده می کنیم:python -m SimpleHTTPServer 8080اگه مثله من از پایتون 3 استفاده می کنید :python3 -m http.server 8080تو مرحله بعد ادرس لوکال خودمونو جای ادرس قبلی میزاریم:تغییر ادرس JKU پیشفرض به ادرس جدیداگه توکن تولید شده رو توی هدر درخواستمون بزاریم و یک ریکوست به سایت بزنیم می بینیم که سرور هنگام برسی توکن ما یک در خواست به JKU جعلی ما ارسال میکنه و عملا توکنی که ساختیم رو با کلید عمومی ما برسی میکنه و 100 درصد تایید میشه ?درخواست دریافت شده &#xA;توی این سناریو ما تونستیم یه توکن با PAYLOAD مورد نظر بسازیم و به سطح دسترسی ادمین برسیم.</description><category>Hassan Mohammadi</category><author>Hassan Mohammadi</author><pubDate>Tue, 21 Dec 2021 14:17:10 +0330</pubDate></item><item><title>جنگو و N+1 کوئری</title><link>https://virgool.ir/@HassanMmdi/django-nplus1-ah3uvgimn0op</link><description>مشکل N+1 یه مسئله عملکردی توی دیتابیس ها هست و این داستان توی خیلی از ORM ها و کوئری های SQL که می نویسیم دیده میشه. توی این پست میخوایم در مورد N+1 کوئری تو جنگو و راه های تشخیص و رفعش صحبت کنیم.سوالی که اینجا پیش میاد n+1 کوئری دقیقا چیه ؟به طور خلاصه فرض کنید لیستی از نتایج یه کوئری داریم و بیایم برای هریک از این نتیجه ها یه کوئری دیگه بزنیم، به مثال زیر توجه کنید :N+1 Queriesbooks = Book.objects.order_by(&#34;title&#34;)&#xA;for book in books:&#xA;    print(book.title, &#34;by&#34;, book.author.name)توی خط اول ما یه QuerySets برای مدل Book ایجاد می کنیم ولی چون QuerySets ها معمولا موجوداتی تنبل هستن عملا هیچ کوئری اجرا نمیشه.توی خط دوم با یه حلقه QuerySets ایجاد شده رو پیمایش می کنیم، همین امر باعث میشه که کوئری اجرا بشه واطلاعات مورد نیاز رو واکشی کنه.توی خط سوم ما به فیلد های هر کدوم از کتاب ها دسترسی داریم، مثله اسم کتاب و... تا اینجا جنگو اینارو با همون یه کوئری گرفته، خوب تا اینجا هیچ مشکلی نیست ولی فیلد نویسنده یه کلید خارجیه و جنگو هم اطلاعات نویسنده رو با همون یه کوئری  نگرفته پس جنگو دوباره اینجا یه کوئری جدید میزنه تا اطلاعات نویسنده رو بگیره.بنابر این یه کوئری برای واکشی همه کتاب ها اجرا می کنیم و N تا کوئری هم برای گرفتن اسامی نویسنده ها برای مثال واسه گرفتن اطلاعات 100 کتاب ما 100+1 تا کوئری میزنیم 1 کوئری برای گرفتن کل کتاب ها و 100کوئری برای اسامی نویسندها،  خوب طبیعتا این روی پرفورمنس دیتابیس تاثیر میزاره و زمان اجرا کوئری هم بالا میره.کوئری هایی که کد ما ایجاد میکنه این شکلیه :SELECT&#xA;      id,&#xA;      title,&#xA;      author_id,&#xA;      ...&#xA;FROM book&#xA;ORDER BY titleو برای گرفتن اسم هر نویسنده هم این کوئری اجرا میشه :SELECT&#xA;      id,&#xA;      name,&#xA;      ...&#xA;FROM author&#xA;WHERE id = %s2N+1 Queriesحالا فرض کنید که ما توی جدول A یه کلید خارجی داریم که مارو ارجا میده به جدول B دارم , توی جدول B یه کلید خارجی به جدول C داریم حالا با کوئری که روی جدول A میزنیم میخوایم اطلاعات مورد نظرمون که توی جدول C هست رو واکشی کنیم، پیچیده شد بزارید این مثالو بزنم، توی مثال بالا ما برای گرفتن اطلاعات نویسنده باید یه کوئری می زدیم حالا فرض کنید میخوایم ببینیم نویسنده توی چه کشوری زندگی میکنه و اطلاعات هر کشور هم توی جدول کشور ها ذخیره شده عملا کوئری ما این شکلی میشه :books = Book.objects.order_by(&#34;title&#34;)&#xA;for book in books:&#xA;    print(&#xA;        book.title,&#xA;        &#34;by&#34;,&#xA;        book.author.name,&#xA;        &#34;from&#34;,&#xA;        book.author.country.name,&#xA;    )برای دسترسی به اطلاعات کلید خارجی country اینجا خودش یه کوئری جدید تولید میشه و عملا کوئری  های ما نسبت به قبل دو برابر میشه ?NM+N+1 Queriesحالا فرض کنید مدل ها رو تغییر دادیم و این امکان وجود داره که یه کتاب رو چندیدن نویسنده نوشته باشه،  کوئری به این شکل درمیاد :books = Book.objects.order_by(&#34;title&#34;)&#xA;for book in books:&#xA;    print(book.title, &#34;by: &#34;, end=&#34;&#34;)&#xA;    names = []&#xA;    for author in book.authors.all():&#xA;        names.append(f&#34;{author.name} from {author.country.name}&#34;)&#xA;    print(&#34;, &#34;.join(names))توی خط پنجم هر کوئری رو N بار اجرا میکنه تا اطلاعات نویسنده های هر کتاب رو دریافت کنیمدر خط ششم به ازای هر نویسنده ما یه کوئری میزنیم تا اطلاعات کشور اون نویسنده رو بگیریم، اگه به طور متوسط M تا نویسنده برای هر کتاب وجود داشته باشه N تا درخواست هم برای گرفتن اطلاعات کشور نویسنده ها وجود داره.چطور این مشکلات رو حل کنیم ?خوب همون طور که مشاهده می کنید این شکل از کوئری ها به شدت روی پرفورمنس پروژه تاثیر میزاره و با بالا رفتن تعداد درخواست ها و حجم دیتای ذخیره شده عملا بعضی جاها سرور دیگه پاسخگو نیست و ممکنه سایت یا پروژه به کلی پایین بیاد و از طرفی چون این شکل از کوئری زدن ها بهینه نیست ما داریم بیخودی منابع سرور رو هدر میدیم.خوب راه حل چیه؟ جنگو دو شکل از QuerySet ها رو پیشنهاد میکنه که با این روش ها میتونیم N تا کوئری  رو به یه کوئری  تبدیل کنیم و عملا این مشکل رو حل کنیم، روش اول select_relatedو روش دوم prefetch_relatedهست که در ادامه به هرکدوم بیشتر میپردازیم.اول بزار فرقشونو بگم بعد بریم قسمت بعدی select_relatedیک کوئری جوین می‌سازه و دیتا رو با یک کوئری می‌گیره ولی prefetch_relatedجوین کردن رو به دیتابیس نمیسپاره و این کارو توی خود پایتون انجام میده، خوب حالا میتونیم بریم قسمت بعدی...select_relatedاول مثال زیر رو مشاهده کنید :books = Book.objects.order_by(&#34;title&#34;).select_related(&#34;author&#34;)&#xA;for book in books:&#xA;    print(book.title, &#34;by&#34;, book.author.name)اینجا دیگه برای گرفتن اطلاعات نویسنده یه کوئری جدید نمیزنه و با استفاده از JOIN تو SQL این دوتا جدول رو باهم ادغام میکنه.کوئری بالا توی SQL این شکلی میشه :SELECT&#xA;      book.id,&#xA;      book.title,&#xA;      book.author_id,&#xA;      ...,&#xA;      author.id,&#xA;      author.name,&#xA;      ...&#xA;FROM&#xA;     book&#xA;     INNER JOIN author ON (book.author_id = author.id)&#xA;ORDER BY book.titleحالا اون 2N+1 رو چطور حل کنیم ? خیلی ساده میتونیم با chained relation این مشکلم حل کنیم مثال زیر رو مشاهده کنید :Book.objects.order_by(&#34;title&#34;).select_related(&#34;author&#34;, &#34;author__country&#34;)باید بگم که متاسفانه select_related توی NM+N+1 Queries نمیتونه به ما کمکی کنه و select_related فقط توی روابط یک به یک یا یک به چند به کار میاد در حالی که NM+N+1 روابط زیادی داره ولی خوشبختانه prefetch_relatedمیتونه با روابط زیادی کار کنه.prefetch_relatedاستفاده از این متد هم تقریبا شکل مثال بالا هست :books = Book.objects.order_by(&#34;title&#34;).prefetch_related(&#34;author&#34;)&#xA;for book in books:&#xA;    print(book.title, &#34;by&#34;, book.author.name)تفاوت این دوتا متد اینجا مشخص میشه که توی این روش دو کوئری اجرا میشه اولی برای گرفتن کتاب ها و دومی نویسنده ها رو بدون تکرار واکشی میکنه. جنگو نویسنده های واکشی شده رو توی حافظه با کتاب های مربوطه پیوند میزنه و توی خط سوم عملا به کوئری جدیدی نیاز نیست.این کوئری هم داخل SQL به این شکله :SELECT&#xA;      id,&#xA;      title,&#xA;      author_id,&#xA;       ...&#xA;FROM book&#xA;ORDER BY titleحالا ما لیستی از شناسه های نویسنده ها رو داریم و به این شکل میتونیم همه نویسنده های مورد نظر رو واکشی کنیم :SELECT&#xA;     id,&#xA;     name,&#xA;     ...&#xA;FROM author&#xA;WHERE id IN (%s, %s, ...)پ.ن به جای s% همون author_id رو قرار میدیم.برای برطرف کردن 2N+1 از این روش استفاده می کنیم :Book.objects.order_by(&#34;title&#34;).prefetch_related(&#34;author&#34;, &#34;author__country&#34;)و همچنین برای بر طرف کردن NM+N+1Book.objects.order_by(&#34;title&#34;).prefetch_related(&#34;authors&#34;, &#34;authors__country&#34;)باید یکم دقت کنید که فرق این دوتا توی چیه... افرین توی s جمع authors، یعنی گفته اطلاعات کشور هریک از نویسنده هاحالا چطور این مشکلات رو تشخیص بدیم ?برای تشخیص N+1 کوئری یه سری ابزار وجود داره که هرکدوم رو توضیح میدیمDjango-debug-toolbarاین ابزار یکی از محبوب ترین پکیج های جنگو هست و برای دیباگ کردن پروژه یکی از بهترین ابزار ها هستnplusoneاین پکیج هشدار N+1 های احتمالی رو به ثبت میرسونه برای مثال :Potential n+1 query detected on `Book.author`&#xA;The Hound of the Baskervilles by Arthur Conan Doyle&#xA;Potential n+1 query detected on `Book.author`&#xA;The Hundred and One Dalmatians by Dodie Smith&#xA;Potential n+1 query detected on `Book.author`&#xA;The Lost World by Arthur Conan Doyle&#xA;[30/Jul/2020 06:23:46] &#34;GET / HTTP/1.1&#34; 200 9495و دیگر پکیج های که با یه سرچ ساده میتونید درموردشون اطلاعات کسب کنید توی این پست من سعی کردم N+1 رو به یه شکل ساده و قابل فهم توضیح بدم امید وارم که پسندیده باشید ❤️Referencesdjango-and-the-n1-queries-problemپ.ن  ترجمه از روی یه مقاله بوده + برداشت های خودمدرضمن این اولین پست من داخل ویرگول بود خوشحال میشم فیدبک بدید که چطور بود و چیکار کنیم که بهتر بشه?</description><category>Hassan Mohammadi</category><author>Hassan Mohammadi</author><pubDate>Wed, 20 Oct 2021 22:53:06 +0330</pubDate></item></channel></rss>