حالا که قدرتِ درک و تحلیلِ درخواست‌های HTTP را به دست آوردیم، می‌خواهیم با این تواناییِ جدید، به سایتِ دیوار سر بزنیم و چند درخواست را با هم‌دیگر تحلیل کنیم.

برای شروعِ کار، ابتدا همه‌ی کوکی‌های خود را که روی دیوار هستند پاک می‌کنیم.

من از Google Chrome استفاده می‌کنم و برای حذف کردنِ کوکی‌ها این‌گونه عمل می‌کنم:

حذفِ کوکی‌ها در گوگل‌کروم

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

حال که تمامِ کوکی‌هایم را حذف کردم، با استفاده از پراکسیِ Burp، درخواستی را به divar.ir می‌فرستم و آن‌را شنود می‌کنم تا محتوایش بررسی کنم. (فرستادنِ درخواست به این‌شکل است که divar.ir را در نوار آدرسِ مرورگرم می‌نویسم و اینتر می‌کنم؛ با این‌کار یک درخواستِ GET باید به دیوار ارسال شود)

درخواستِ اولیه به دیوار

درخواستِ بالا یک درخواستِ کاملاً ساده است که به سادگی باید بتوانید محتوایش را درک کنید؛ این درخواست برای آدرسِ / در divar.ir یک درخواستِ GET می‌فرستد.

شمای ساده‌ی درخواست به دیوار

حال وقتِ آن‌است که با استفاده از کلیدِ کلیدِ Forward در BurpSuite، این درخواست را به سرور انتقال دهیم؛ یعنی اجازه‌ی عبورِ آن از Burp به سرور را بدهیم.

اگر صبر کنیم تا صفحه‌ی دیوار در مرورگرمان لود شود، و سپس به Burp بازگردیم و به تبِ History برویم، می‌توانیم درخواستِ مذکور را ببینیم.

با انتخابِ آن در پایینِ صفحه دو تب ظاهر می‌شوند که یکی Request و دیگری Response است.
همان‌طور که احتمالاً حدس زده‌اید، Request همان درخواستی است که به سرور ارسال شده است، و Response نیز پاسخی است که سرور به آن درخواست داده است.

ما می‌خواهیم ببینیم در پاسخ به درخواستی که مرورگرمان برای دیدنِ صفحه‌ی اولِ دیوار به سرورِ آن ارسال کرده، چه پاسخی ارسال شده است، پس تبِ Response را فعال می‌کنیم و محتوایِ آن را با هم به نظاره می‌نشینیم!

HTTP/1.0 200 OK
Date: Fri, 23 Oct 2015 12:14:18 GMT
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Server: gunicorn/19.3.0
Set-Cookie: did=8sFri2f-w5gXAQ; Domain=.divar.ir; expires=Sat, 27 May 2023 12:45:32 GMT; Path=/
Connection: close

در خطِ اول وضعیتِ پاسخ مشخص شده است؛ 200، کدِ وضعیت است؛ که به معنایِ این است که همه‌چیز OK است. (می‌توانید کدهای وضعیت را در این‌جا به صورتِ مفصل بخوانید)

تاریخ، نوعِ محتوا و برخی اطلاعات در موردِ سرور و نتیجه‌ی درخواست در خطوطِ بعدی آمده است؛ تنها موردِ قابلِ توجه در این پاسخ می‌تواند Set-Cookie باشد که از مرورگرِ ما می‌خواهد کوکیِ جدیدی را با نامِ did و با مقدارِ 8sFri2f-w5gXAQ، روی هر دامنه‌ای که به صورتِ divar.ir.* مشخص می‌شود(به علاوه خودِ divar.ir) ست کند و از این پس آن را با درخواست‌هایش ارسال کند. (همچنین یک Path و زمانِ انقضا برای آن تعیین شده است؛ که Path آدرسی را تعیین می‌کند که از آن به پایین‌تر، این کوکی باید ارسال شود؛ مثلاً اگر mamad/ باشد، آن کوکی برای mamad/salam/ و هر آدرسی زیرِ mamad/ ارسال خواهد شد)

حال اگر به مرورگرمان مراجعه کنیم و کوکی‌ها را بررسی کنیم؛ باید این کوکیِ جدید را داشته باشیم.

کوکیِ تازه‌ست شده توسطِ دیوار


#خارج‌از‌محدوده

حال ممکن است سوالی برایتان پیش آید بدین شرح که کوکی‌های دیگر چطوری ست شده‌اند؟ ما که در درخواست فقط یک کوکی را داشتیم که قرار بود ست شود!

پاسخ در Sourceــِ این صفحه نهفته است!

این صفحه برخی اسکریپت‌هایی را از سایت‌های دیگر لود می‌کند(من جمله از Google Analytics):

الصاق‌شدنِ اسکریپتِ گوگل آنالیز به دیوار

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

سوالِ دیگری که ممکن است ذهنِ تشنه‌ی پرسشِ شما را مشغول کند، این است که اگرچه این اسکریپت به صفحه الصاق شده است، اما من چرا قادر نیستم درخواستی که مرورگرم برای گرفتنِ آن می‌فرستد را مشاهده کنم؟

پاسخ در مفهومی به نامِ Cache نهفته شده است. یک مرورگر زمانی که یک‌بار برای یک المان از صفحه درخواست می‌فرستد، آن‌را Cache می‌کند و دیگر بارِ دیگر آن‌را به طورِ کامل لود نمی‌کند و همان نسخه‌ی قبلی را روی صفحه بارگذاری می‌کند.
اگر مایلید مرورگر Cacheــِ خود را برای صفحه‌ی فعلی خالی کند و از صفر برای همه‌ی المان‌ها درخواست بدهد، از کلید‌های ترکیبیِ CTRL+F5 استفاده کنید:

دیدنِ همه‌ی درخواست‌ها با کلیدهای CTRL+F5


لازم به ذکر است که در ادامه‌ی پاسخِ درخواست، محتوای صفحه به زبانِ HTML برای مرورگرمان ارسال شده است. اگر با HTML آشنایی ندارید بهتر است هر چه سریع‌تر کمی در موردِ آن بخوانید.
حال که می‌دانیم در درخواستِ اول و پاسخِ آن چه اتفاقی می‌افتد، می‌توانیم عمل‌گرایی کنیم و این درخواست را در Python شبیه‌سازی کنیم.
هدفِ اول: پیدا کردنِ یک لیستِ کامل از لینک‌هایی که به شعبه‌های مختلفِ دیوار(برای شهرهای مختلف) منتهی می‌شوند.
ما می‌دانیم که اطلاعاتِ مربوط به شهرها در پاسخِ اولین درخواست به دیوار برای مرورگرِ ما ارسال می‌شوند؛ منظورم جدولِ زیر است که با درخواستِ اول به دیوار برای ما قابل مشاهده است:
جدولِ شهرها در دیوار
پس می‌توانیم با ارسالِ یک درخواست، این لیست را بگیریم!
ما می‌دانیم که در پاسخِ درخواستِ اولی که به دیوار فرستادیم، محتوای صفحه به صورتِ HTML برای ما ارسال شد(علاوه بر برخی هدرها که پیش‌تر آن‌ها را بررسی کردیم)، پس با بررسیِ سورسِ صفحه که به صورتِ HTML است، خواهیم توانست تا این لیست را در آن‌جا پیدا کنیم و بدانیم کدام قسمت از کدها به این جدول مربوط است.
برایِ دیدنِ سورسِ صفحه روی یک قسمتِ خالی از آن کلیک راست کرده و روی View Page Source کلیک می‌کنیم تا به صفحه‌ی مشاهده‌ی Sourceــِ آن منتقل شویم.(فرضِ من این است که شما یک آشناییِ اولیه با HTML دارید و می‌دانید در سورس چه می‌گذرد)
در صفحه‌ی سورس، با کمی جستجو به عباراتِ زیر می‌رسیم:
<div class="city_list_desktop"><a href='http://tehran.divar.ir/?saveLoc=1' class='row single-col'>
...

همان‌گونه که می‌بینید یک تگِ a به مقصدِ http://tehran.divar.ir/?saveLoc=1 باز شده است(یعنی به مقصدِ دیوار، واحدِ تهران)، اگر به صفحه‌ای اصلی برگردیم و مقصدِ یکی از لینک‌هایی که در جدول هستند را به شکلِ زیر کپی کنیم:

کپی‌کردنِ آدرسِ یک لینک

و سپس نگاهی به آدرسِ آن بیندازیم، به شکلِ زیر خواهد بود:

http://urmia.divar.ir/?saveLoc=1

همان‌طور که مشاهده می‌کنید بسیار به لینکی که در سورسِ صفحه پیدا کرده بودیم شباهت دارد، در نتیجه می‌توانیم حدس بزنیم که محلِ این لینک‌ها را در سورس یافته‌ایم.

من چندتا از لینک‌ها را در زیر لیست کرده‌ام، می‌خواهیم با یافتنِ یک الگوی مشترک در ساختارِ آن‌ها، یک Regular Expression(عبارتِ منطقی) برای آن‌ها بنویسیم و در نهایت همه‌ی آن‌ها را با یک حرکت از کلِ سورسِ صفحه جدا کنیم.

http://tehran.divar.ir/?saveLoc=1
http://karaj.divar.ir/?saveLoc=1
http://mashhad.divar.ir/?saveLoc=1
...

می‌توان چنین الگویی برای این‌چنین آدرس‌هایی ساخت:

http://{نامِ شهر}.divar.ir/?saveLoc=1

در نتیجه به سادگی قابلِ تشخیص است که صرفاً با داشتنِ «نامِ شهر»، می‌توانیم کلِ لینک را بسازیم(زیرا بقیه‌ی لینک ثابت است).

حال فرض می‌کنیم این دو را داریم:

۱- محتوایِ HTMLــِ صفحه

۲- یک الگوی منطقی برای نامِ شهرها(که بینِ //:http و divar.ir. قرار دارند)

در این‌گونه مواقع که ما یک الگوی منطقی و یک متنِ بزرگ داریم(و می‌خواهیم در متنِ بزرگ به دنبالِ متنی که از الگو پیروی می‌کند بگردیم)، از Regex استفاده می‌کنیم؛ Regex مخففِ Regular Expressions، یا عباراتِ منطقی است!
همان‌طور که از اسمش پیداست، دقیقاً چاره‌ی کارِ ما در شرایطِ فعلی است.

من برای بررسی و ساختِ Regexهایم، معمولاً از سایتِ خوبِ regex101 استفاده می‌کنم. (امیدوارم با خواندنِ این فایلِ بسیار خوب، به درکِ خوبی از رگکس و نحوه‌ی کار با آن برسید)

برای شروع ابتدا متنی که قرار است روی آن Regex بزنیم را روی regex101 و در قسمتِ Test String کپی می‌کنیم. (متنِ مذکورِ همان سورسِ صفحه است)

حال باید Regexــِ خود را در قسمتِ Regular Expression وارد کنیم.

من این عبارت را ساختم(خودتان در صورتِ آشنایی با Regex به سادگی می‌توانید عباراتی مانندِ این(و البته بهتر) را بسازید):

http://([^\.]+)\.divar\.ir/\?saveLoc=1

(توجه داشته باشید که از سمتِ چپِ صفحه، Flavor را روی Python ست کنید؛ زیرا ما قرار است روی Python کد بزنیم و رگکسِ فوق هم با رسم‌الخطِ پایتون نوشته شده است)

توضیحِ Regex: این رگکس یک عبارت که نقطه‌ای ندارد را، مابینِ دو عبارتِ زیر Match می‌کند:

http:// & .divar.ir/?saveLoc=1

دقت کنید که ما از نقطه به عنوانِ جداکننده استفاده کرده‌ایم.


اگر این Regex را در صفحه‌ی مذکور بنویسید و کمی صبر کنید، عبارتِ زیبایِ رگکس با یک عبارت می‌خوانده است را مشاهده خواهید کرد؛ این یعنی رگکسِ ما با یک عبارت تناسب داشته است و می‌توانیم آن عبارت را در سمتِ راستِ صفحه(Match Information) ببینیم.

همان‌طور که مشاهده خواهید کرد، تنها عبارتِ tehran برای ما Match شده است؛ اما ما به خوبی می‌دانیم این رگکس باید بیش از یک عبارت را Match کند؛ این مشکل از آن‌جا ناشی می‌شود که ما Flagـه(ـــِ دیده نمی‌شود) g را برای رگکسمان انتخاب نکرده‌ایم، فلگِ g، مشخص می‌کند که این رگکس می‌تواند بیش از یک عبارت را نیز مچ کند.
پس اگر g را در کادرِ روبروی regexـمان بنویسیم، مشاهده می‌کنیم که تعداد Matchها بیشتر می‌شود و به تعدادِ مطلوب(تری!) می‌رسد.

رگکس مواردِ بیشتری را مچ کرده است

اما اگر به لیستِ شهرها بنگریم:

جدولِ شهرها در دیوار

خواهیم دید که تنها 44شهر داریم، اما این Regex تعدادِ 88 مقدار پیدا کرده است. با کمی دقت متوجه می‌شویم که Regex از هر شهر دو مقدار در متن پیدا کرده است، این برای آن است که توسعه‌دهندگانِ دیوار یک بار برای نسخه‌ی موبایل، و یک بار برای نسخه‌ی دسکتاپِ سایت این جدول را در سورس قرار داده‌اند(نسخه‌ی دسکتاپ در موبایل، و نسخه‌ی موبایل در دسکتاپ نشان داده نمی‌شود).

پس کافی است لیستِ مذکور را نصف کنیم و فقط نیمه‌ی اول را در نظر بگیریم؛ زیرا در نیمه‌ی دوم همه‌چیز تکراری است.

کد زدن!

حالا می‌خواهیم کمی کد بزنیم.

اگر در regex101، در سمتِ چپ روی code generator کلیک کنید، می‌بینید که به صورتِ آماده کدهای پایتون در اختیارتان قرار خواهد گرفت.

import re
p = re.compile(ur'http://([^\.]+)\.divar\.ir/\?saveLoc=1')
test_str = "{source goes here}"
re.findall(p, test_str)

در خطِ اول کتابخانه‌ی re که مربوط به Regular Expressionها در پایتون می‌شود، به فایل متصل شده است.

خطِ دوم رگکسِ ما را کامپایل و آن‌را آماده برای مچ کردنِ یک عبارت گردانده است.

در خطِ سوم سورسِ ما قرار است به صورتِ رشته‌ای در test_str قرار بگیرد.

در خطِ آخر قرار است رگکسِ ما با این رشته مچ شود.

پس دست‌به‌پایتون می‌شویم(بله! انتظار دارم شما کمی هم پایتون بلد باشید!)، ابتدا باید محتوای صفحه را به صورتِ یک رشته در test_str بریزیم، برای این امر با سرچِ عبارتِ زیر:

python url contents to string

به این قطعه‌کد می‌رسیم:

import urllib

link = "http://www.somesite.com/details.pl?urn=2344"
f = urllib.urlopen(link)
myfile = f.read()
print myfile #myfile is a string

پس فایلمان را به این شکل اصلاح می‌کنیم:

import re
import urllib

p = re.compile(ur'http://([^\.]+)\.divar\.ir/\?saveLoc=1')
link = "http://divar.ir"
f = urllib.urlopen(link)
test_str = f.read()
re.findall(p, test_str)

یعنی محتوای لینک را به صورتِ رشته در test_str ریختیم، بعد آمدیم و با re.findall، آن‌را با رگکسِ p(که تعریف کرده‌ایم) مچ کردیم.

اگر الان فایلِ فوق را اجرا کنیم، خروجیِ ما خالی خواهد بود؛ بسیار بدیهی است، زیرا ما اولاً نتیجه‌ی Regex را جایی ذخیره نکرده‌ایم، ثانیاً چیزی چاپ نکرده‌ایم(در حقیقت فقط ثانیاً!).

پس ابتدا خروجیِ findall را در یک متغیر ذخیره می‌کنیم:

...
re_out = re.findall(p, test_str)

خروجیِ re.findall، که به متغیرِ re_out می‌ریزد، یک لیست است که حاویِ همه‌ی مقادیری است که با Regexـمان مچ بوده‌اند.

re_out: ['tehran', 'karaj', 'mashhad', 'isfahan', 'tabriz', 'shiraz', 'ahvaz', 'qom', 'kermanshah', 'urmia', 'zahedan', 'rasht', 'kerman', 'hamedan', 'arak', 'yazd', 'ardabil', 'bandar-abbas', 'qazvin', 'zanjan', 'gorgan', 'sari', 'dezful', 'abadan', 'bushehr', 'borujerd', 'khorramabad', 'sanandaj', 'eslamshahr', 'kashan', 'najafabad', 'ilam', 'kish', 'birjand', 'semnan', 'shahrekord', 'mahshahr', 'yasuj', 'bojnurd', 'behbahan', 'sabzevar', 'masjed-e-soleyman', 'neyshabur', 'shushtar', 'tehran', 'karaj', 'mashhad', 'isfahan', 'tabriz', 'shiraz', 'ahvaz', 'qom','kermanshah', 'urmia', 'zahedan', 'rasht', 'kerman', 'hamedan', 'arak', 'yazd','ardabil', 'bandar-abbas', 'qazvin', 'zanjan', 'gorgan', 'sari', 'dezful', 'abadan', 'bushehr', 'borujerd', 'khorramabad', 'sanandaj', 'eslamshahr', 'kashan', 'najafabad', 'ilam', 'kish', 'birjand', 'semnan', 'shahrekord', 'mahshahr', 'yasuj', 'bojnurd', 'behbahan', 'sabzevar', 'masjed-e-soleyman', 'neyshabur', 'shushtar']

کافی‌است محتویاتِ آن را چاپ کنیم:

import re
import urllib

p = re.compile(ur'http://([^\.]+)\.divar\.ir/\?saveLoc=1')
link = "http://divar.ir"
f = urllib.urlopen(link)
test_str = f.read()
re_out = re.findall(p, test_str)
for city in re_out:
	print city
C:\Users\Mamad\Desktop>python test.py
tehran
karaj
mashhad
isfahan
tabriz
...

تنها دو مرحله‌ی دیگر باقی‌است:
۱- اضافه کردنِ مقادیرِ ثابت به لینک‌ها (http و اضافاتِ انتها)
۲- چاپ نکردنِ نیمه‌ی دومِ لیست

پس کد را به شکلِ زیر ویرایش می‌کنم:

import re
import urllib

p = re.compile(ur'http://([^\.]+)\.divar\.ir/\?saveLoc=1')
link = "http://divar.ir"
f = urllib.urlopen(link)
test_str = f.read()
re_out = re.findall(p, test_str)
for city in re_out[:(len(re_out)/2)]: # یعنی همه‌ی محتویاتِ لیست تا وسط
	print "http://" + city + ".divar.ir/?saveLoc=1"

حال اگر به خروجی بنگریم می‌بینیم که به کامِ دل رسیده‌ایم:

C:\Users\Mamad\Desktop>python test.py
http://tehran.divar.ir/?saveLoc=1
http://karaj.divar.ir/?saveLoc=1
http://mashhad.divar.ir/?saveLoc=1
...

حال اگر مایلید خروجیِ برنامه‌ی خود را در یک فایل ذخیره کنید تا بتوانید حالش را ببرید، کافیست دستورِ زیر را اجرا کنید:

python test.py > out.txt

فایلِ پایتون، و خروجیِ من، از این‌جا قابلِ دریافت هستند.