Keş Zəhərlənməsi | Cache Poisoning
Bloqumuzun əsas məqsədi bu sahədə Azərbaycan dilində yazılan məzmunu genişləndirməkdir. Bu məqalə həmin istiqamətdə başlanğıcdır. Məqalə digər mövzular haqda da ola bilərdi, amma istərdim ki, həm “Senior”lar, həm də “Junior”lar üçün oxunaqlı və maraqlı bir mövzu olsun. Əsas mövzu “Keş Zəhərlənməsi”dir. Biz burada “CVE-2016-2784”ü yoxlayacağıq. Bu məqalənin məqsədi təkcə “Keş Zəhərlənməsi”ni izah etmək deyil, həm də sizə “Patch-Diff"i göstərmək və başlıqların funksiyalarını daha yaxşı başa salmaqdır.
Məzmun
Keş (Cache)
Başlıqlar (Headers)
Keş Zəhərlənməsi (Cache Poisoning)
CVE-2016-2784
Lüğət
Bağlantılar - Link/URL
Başlıq - Header (Host, Conent-Type, Accept və s.)
Həssas - Vulnerable
Keş
Keş nədir?
Keş məlumatların axtarışını sürətləndirmək və ümumi sistemin işini yaxşılaşdırmaq üçün tez-tez əldə edilən məlumatları və ya təlimatları daha asan əldə edilən yerdə saxlayan yüksək sürətli aparat və ya proqram təminatı komponentidir. Daha sadə desəm, keş yenidən harasa hər hansı sorğunu göndərməmək üçün müvəqqəti saxladığımız məlumatdır.
Keşin bir neçə növü var, lakin daha çox mövzuya aid olanlardan 2-ni sadalayacağam.
- Veb Brauzer Keşi: “Chrome”, “Firefox” və “Safari” kimi veb brauzerlər - veb səhifələrin, şəkillərin və digər resursların surətlərini kompüterinizdə (lokal olaraq) saxlayır. Bu, veb-səhifəyə yenidən baxdığınız zaman veb məzmunun daha sürətli yüklənməsinə imkan verir, çünki onu internetdən yenidən yükləmək əvəzinə keşdən əldə etmək olur.
- Server tərəfində keş: Veb serverlər hər bir istifadəçi sorğusu üçün məzmunu sıfırdan yaratmaq əvəzinə, tez-tez əldə edilən məzmunu birbaşa serverdə saxlayır və onları istifadə edir. Eyni məlumatları təkrar emal etmək və ya dinamik məzmun yaratmaq ehtiyacını minimuma endirməklə server yükünü və cavab vaxtlarını azaldır.
Bəzi server tərəfində keşləmə mexanizmləri:
Obyekt Keşi
Obyektin keşlənməsi verilənlər bazası sorğularının keşlənməsinə əsaslanır. Bütün “HTML” səhifələrini keşləşdirməkdənsə, o, verilənlər bazası sorğularının nəticələrini, “API” cavablarını yaddaşda və ya diskdə saxlayır. Bu, sonrakı sorğularda eyni sorğu və ya hesablamanı yenidən yerinə yetirmək əvəzinə keşlənmiş məlumatları əldə etməyə imkan verir.
Opcode Keşi
“Opcode” (əməliyyat kodu) kompüterin mərkəzi prosessorunun (CPU) birbaşa icra edə biləcəyi xüsusi əməliyyatı təmsil edən aşağı səviyyəli (low-level), maşın tərəfindən oxuna bilən koddur.
“Opcode keşlənmə”si “PHP” kimi server tərəfi skript dillərinə xasdır. ”PHP” kodunu yazdığınız zaman o, ilkin olaraq insan tərəfindən oxuna bilən formada olur. “PHP” skriptləri server tərəfindən icra edilməzdən əvvəl əməliyyat koduna yığılır. “Opcode keşləmə” tərtib edilmiş kodu yaddaşda saxlayır ki, o, sonrakı sorğular üçün təkrar istifadə olunsun və serverin hər dəfə skripti yenidən tərtib etməsinə ehtiyac qalmasın.
“Opcode keşləmə”sini istifadə edən nümunə “CMS Typo3”dür (https://docs.typo3.org/m/typo3/guide-installation/9.5/en-us/In-depth/OpcodeCache/Index.html)
CDN (Məzmun Çatdırılma Şəbəkəsi) Keşi
“CDN keşlənməsi” Məzmun Çatdırılma Şəbəkələri tərəfindən həyata keçirilən keşləmə formasıdır. “CDN”lər müxtəlif coğrafi yerlərdə yerləşən serverlərin paylanmış şəbəkələridir. Onlar istifadəçinin olduğu yerə ən yaxın olan kənar serverlərindən şəkillər, üslub cədvəlləri, “JavaScript” faylları kimi statik aktivləri (asset) keşləyir.
Mən öz veb saytımıza əsaslanaraq veb-brauzer keşinin sadə nümunəsini təqdim edə bilərəm. Veb saytımızı dəfələrlə yükləsəniz və sonra interneti söndürsəniz, “Page could not be loaded” xətası ilə keşlənmiş səhifə alacaqsınız.
Siz bunu edərkən inkişaf alətlərini (developer tools) açsanız, “Transferred” altında “service worker"i görəcəksiniz. Xidmət işçisi (service worker) proksi server kimi fəaliyyət göstərir və cavabları öz keşindəki məlumtalar ilə əvəz etməyə imkan verir ki, hansı ki, hal-hazırda baş verdi. Qısaca, bu bizim kompüterimizdə (lokal-da) olan keşdir.
Sizə server tərəfindəki keşi, brauzer keşi ilə eyni praktiki şəkildə göstərmək üçün biz server tərəfində keşləmə xüsusiyyətinə malik real “CMS” qurmalıyıq. Ancaq ondan əvvəl keşləmədə istifadə olunan başlıqları daha yaxşı başa düşək.
Başlıqlar
Cache-Control
Cache-Control ən vacib HTTP keşləmə başlığıdır. Buraya keşləmə davranışına nəzarət etmək üçün bir neçə direktiv daxildir:
- Public/private: Cavabın hər hansı keş (public) və ya yalnız müştərinin brauzeri (private) tərəfindən keşlənə biləcəyini göstərir. Direktiv “public” olaraq qeyd olunduqda sorğu-cavab zənciri boyunca istənilən keş ilə yaddaşda saxlanıla bilər. Buraya müştərinin brauzer keşi, vasitəçi proksilər və hətta “Məzmun Çatdırılma Şəbəkələri”(CDN) daxildir. Məsələn: Vebsaytdan açıq şəkildə keşlənmiş şəkil istifadəçinin brauzer keşində, ISP-nin proksi keşində və bütün dünya üzrə “CDN” serverlərində saxlanıla bilər. “Private” olaraq qeyd olunduqda isə, ancaq müştərinin brauzeri tərəfindən keşlənə bilər.
- No-cache: Keşlənmiş nüsxəni (copy) buraxmazdan əvvəl keşləri yoxlama üçün mənbə serverinə sorğu göndərməyə məcbur edir. “No-Cache” rejimində hər dəfə resurs tələb edildikdə, keşlər keşlənmiş nüsxənin hələ də təzə olub-olmadığını yoxlamaq üçün sorğunu mənbə serverinə təqdim etməlidir. Yoxlama adətən “ETag” və ya “Last-Modified” başlıqlarından istifadə etməklə həyata keçirilir. “ETag” server tərəfindən resursun xüsusi versiyasına təyin edilmiş unikal identifikatordur. Müştəri resurs tələb etdikdə keşlənmiş surətinin “ETag” dəyərini ehtiva edən “If-None-Match” başlığını göndərə bilər. Server bu “ETag”ı cari resursun “ETag”ı ilə müqayisə edir. Əgər onlar uyğun gəlirsə, bu o deməkdir ki, resurs dəyişməyib və server keşlənmiş versiyanın istifadə oluna biləcəyini göstərən “304 Not Modified”ə cavab verir.
Məsələn: “Gif”ə baxmaq üçün sorğu göndərdik. “ETag” və “Last-Modified” başlıqları ilə cavab aldım.
Aldığım “ETag” başlığı “If-None-Match” başlığı kimi göndəriləcək və “Last-Modified” başlığı “If-Modified-Since” başlığı kimi göndəriləcək. Burada server göndərdiyimiz “ETag”ı (If-None-Match-ın dəyəridir) cari resursun “ETag”ı ilə müqayisə edir və gördüyünüz kimi onlar uyğun gəlir, ona görə də cavabın status kodu 304-dür.
Belə bir sual yarana bilər. Şəkil dəyişsə, nə olacaq? Əgər tələb etdiyimiz resurs dəyişdirilibsə və biz (müştəri tərəf) bunu bilmiriksə, köhnə “İf-None-Match” və “If-Modified-Since” başlıqları ilə sorğu göndəriləcək və “ETag”lar uyğun gəlmirsə, server bizim istifadə edəcəyimiz yeni “ETag” və “Last-Modified” başlığı ilə cavab verəcək. Yeni başlıqlar gələcək sorğularda istifadə olunacaq.
- No-store: Keşlərə cavabı heç bir şəraitdə saxlamamağı əmr edir.
- Max-age=[saniyələr]: Resursun təzə hesab ediləcəyi maksimum vaxtı müəyyən edir.
- S-maxage=[saniyələr]: Maksimum yaşa (max-age) oxşardır, lakin bu yalnız paylaşılan keşlərə aiddir (CDN keşləri kimi). Tutaq ki, xəbər saytı məqalələri yaymaq üçün “CDN”dən istifadə edir. Həmin saytda belə başlıq ola bilər “Cache-Control: s-maxage=600, max-age=300”. Bu, paylaşılan keşlərə (CDN kimi) bildirir ki, onlar məqaləni 10 dəqiqə (s-maxage=600), lakin brauzer onu yalnız 5 dəqiqə (maksimum yaş=300) yaddaşda saxlamalıdır.
- Must-revalidate: Keşin yenidən doğrulanması lazım olduğunu bildirir. Keşlənmiş resursun təzəlik müddəti (max-age, s-maxage və ya Expires başlığı ilə müəyyən edilir) bitdikdə köhnəlir. “Must-revalidate” direktivi, istifadəçilərin köhnəlmiş məzmunu görməməsini təmin edərək köhnə resursun yenidən istifadə edilməzdən əvvəl hələ də etibarlı olub-olmadığını yoxlamaq üçün keşləri mənbə serverindən (ETag və ya ”Last-Modified” istifadə etməklə) yoxlamağa məcbur edir.
- Proxy-revalidate: “Must-revalidate” ilə oxşardır, lakin bu, yalnız paylaşılan keşlərə aiddir.
- No-transform: Hər hansı bir vasitəçiyə (proksilər kimi) cavab məlumatlarını dəyişdirməyi qadağan edir. Məsələn: Mobil şəbəkə (Azercell və s.) bant (bandwidth) genişliyinə qənaət etmək üçün şəkilləri aşağı keyfiyyətə çevirə bilər. “No-transform” istifadə olunanda, bu cür dəyişikliklər qadağan olur.
Praqma
Pragma: no-cache: Keşlənmiş nüsxəni buraxmazdan əvvəl yoxlama üçün sorğunu mənbə serverinə təqdim etmək üçün keşlərə göstəriş vermək üçün tarixən istifadə edilən HTTP/1.0 başlığıdır. İndi onu əsasən “Cache-Control: no-cache” əvəz edir.
Expires
Expires: Keşlənmiş resurs köhnəldikdə mütləq vaxt damğası (timestamp) təqdim edir. O, “Cache-Control”un “max-age” direktivi kimidir, əgər hər ikisi varsa, “Cache-Control” tərəfindən ləğv edilir.
Digər Başlıqlar
Age: Resursun proksi keşində nə qədər müddət (saniyələrlə) saxlandığını göstərir.
Vary: Sorğu başlıqlarına əsasən cavabın müxtəlif versiyaları təqdim edildikdə istifadə olunur.
Təsəvvür edin ki, iki formatda şəkillərə xidmət edən veb saytınız var: “WebP” və “JPEG”. “WebP”i bütün brauzerlər dəstəkləmir, “Chrome” isə dəstəkləyir. Veb-brauzer, veb saytınızda bir şəkil üçün sorğu göndərdikdə, sorğuda “Accept” başlığını ehtiva edir. Bu “Accept” başlığı serverinizə müştərinin hansı məzmun növlərini emal edə biləcəyini bildirir. “WebP”ni dəstəkləyən brauzerlər üçün “Accept” başlığına “image/webp” daxil olacaq və bu, “WebP” şəkillərinin məqbul olduğunu göstərir.
“Vary: Accept” başlığının mövcudluğu keşləmə sisteminin müxtəlif “Accept” başlıqları ilə tələb olunarsa, eyni resursun çoxsaylı versiyasını saxlamasını təmin edir. Beləliklə, əgər “Chrome” brauzeri şəkli tələb edərsə, keş bu versiyanı “Accept: image/webp” sorğu başlığına qarşı açarla saxlayacaq. Əgər daha sonra köhnə brauzer eyni şəkli tələb edirsə, server şəklin “JPEG” versiyası ilə cavab verəcək və keş bu versiyanı ayrı-ayrılıqda saxlayacaq.
Keş Zəhərlənməsi
Daha əvvəl izah etdiyim başlıqları unuda bilərsiniz, çünki onlar brauzer “keş zəhərlənməsi” üçündür. Biz server tərəfindəki “keş zəhərlənməsi”ni araşdıracağıq.
Server tərəfində “keş zəhərlənməsi” veb serverdə və ya aralıq keş serverində (əks proxy və ya məzmun çatdırma şəbəkəsi (CDN) kimi) saxlanılan keşi hədəfləyir. Server tərəfində “keş zəhərlənməsi”ndə təcavüzkar serverin keşlənmiş məzmunu saxlaması və əldə etməsi prosesini manipulyasiya edir. Məsələn: Veb proqramda və ya serverin konfiqurasiyasındakı boşluqdan istifadə etməklə, təcavüzkar qəsdən hazırlanmış cavabın keşləşdirilməsinə səbəb ola bilər. Həmin resurs üçün sonrakı sorğular daha sonra zəhərlənmiş məzmuna birbaşa keşdən xidmət göstərəcək və ona daxil olan bütün istifadəçilərə təsir edəcək.
Məsələn: Mənim 2 səhifəm var, onların hər birində başqa birinə yönləndirən bir keçid var.
Birinci səhifənin kodu:
Code:
<?php
$host = $_SERVER['HTTP_HOST'];
$uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
$page2Link = "http://" . $host . $uri . "/page2.php";
?>
<!DOCTYPE html>
<html>
<head>
<title>Page 1</title>
</head>
<body>
<h1>This is Page 1</h1>
<p><a href="<?php echo $page2Link; ?>">Go to Page 2</a></p>
</body>
</html>
İkinci səhifənin kodu:
Code:
<?php
$host = $_SERVER['HTTP_HOST'];
$uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
$page1Link = "http://" . $host . $uri . "/page1.php";
?>
<!DOCTYPE html>
<html>
<head>
<title>Page 2</title>
</head>
<body>
<h1>This is Page 2</h1>
<p><a href="<?php echo $page1Link; ?>">Go back to Page 1</a></p>
</body>
</html>
Birinci səhifəni açanda məni ikinci səhifəyə yönləndirən bir keçid aldım. Yuxarıdakı “PHP” kodu, hostu (172.16.240.142-ü) “Host” başlığından alır.
Host başlığını redaktə etsək, yönləndirmə “URL”i də redaktə olunacaq. Əgər başqa istifadəçi bu səhifəyə daxil olarsa, onlar redaktə etdiyimiz “URL”i əldə edəcəklər, çünki o, keşdə saxlanmışdır. Server tərəfində keşləmə ümumiyyətlə başlıqları ehtiva etməyə bilər, buna görə də cavabda heç bir başlıq görmürsünüzsə, bu normaldır, lakin keşləmə hələ də baş verir.
Bu problemi həll etməyin bir çox yolu var (keşləməni söndürməkdən başqa). Məncə, ən rahatı nisbi bağlantılardan istifadə etməkdir.
Code:
<?php
$page1Link = "page1.php";
?>
<!DOCTYPE html>
<html>
<head>
<title>Page 2</title>
</head>
<body>
<h1>This is Page 2</h1>
<p><a href="<?php echo $page1Link; ?>">Go back to Page 1</a></p>
</body>
</html>
Bu, çox güman ki, bir çox “CVE”də rastlaşacağınız “keş zəhərlənməsi”nin ən əsas nümunəsidir. Bu məqaləni oxumağı məsləhət görürəm - https://portswigger.net/research/practical-web-cache-poining. “Portswigger”in Tədqiqat Direktoru Ceyms Kettle tərəfindən yazılmışdır. O, “Unity”, “Red Hat”, “Mozilla” və s.-də aşkar etdiyi “keş zəhərlənməsi” zəifliklərini müzakirə edir.
Bunu oxuyursunuzsa, ağlınızda belə bir sual yarana bilər: “Biz bir keçidin Host başlığı vasitəsilə dəyişdirilə biləcəyini görmək üçün niyə bu gədər oxuduq?” Mən sizə başqa başlıqlardan nümunələr təqdim edə bilərəm və ya Ceyms Kettlenin yazdıqlarını təkrarlaya bilərəm, lakin siz onu özünüz də oxuya bilərsiniz. Bu nöqtəyə qədər oxumaq, “Portswigger”in araşdırmasını oxuyanda, ağlınızda sual qoymamalıdır.
CVE-2016-2784
“CMS” “Made Simple” 2.x-dən 2.1.3-dək və 1.x-dən 1.12.2-dək, “Smarty Cache” aktivləşdirildikdə, “keş zəhərlənməsi”nə qarşı həssasdır. Sınamalı başqa “CVE”lər də var idi, lakin mən onların “GitHub” repozitoriyasını tapa bilmədiyim üçün bunu seçmək qərarına gəldim və sizə “Patch Diff”i göstərmək istədim.
“Patch Diff” təhlükəsizlik yamaqlarına (patch) xüsusi diqqət yetirməklə binar faylların müxtəlif versiyaları arasında dəyişikliklərin təhlili prosesidir. Tutaq ki, bizdə eyni məhsulun iki versiyası var, 2.1.2 və 2.1.3. Biz bilirik ki, 2.1.2 versiyası “keş zəhərlənməsi”nə qarşı həssasdır, lakin gəlin təsəvvür edək ki, zəifliyin harada olduğunu bilmirik. Koddakı fərqləri görmək və zəifliyin haradan yarandığını və onun necə düzəldildiyini anlamaq üçün 2.1.3 versiyasını yükləyə və onu həssas 2.1.2 versiyası ilə müqayisə edə bilərik. Bu termin bizim “infosec icma”mız üçün nisbətən yeni ola bilər və geniş tanınmır.
Ətraf mühitin (enviroment) qurulmasının əsasları ilə başlayaq. Bu “CVE”nin tarixi 2016-cı ilə aiddir, ona görə də məsləhətim budur ki, “Ubuntu 20.04”ü o zaman istifadə edilmiş “MySQL” və “PHP” versiyaları ilə birlikdə yükləyəsiniz. Sizə mühiti qurmaq üçün addım-addım əmrlər verə bilərəm və ya “Vmware” şəklini paylaşa bilərəm, ancaq özünüz qursanız, bu daha effektiv olar. Bununla bağlı sualınız olarsa, ictimai şəkildə yaza bilərsiniz. Mən cavablandıra bilməsəm də, başqa şəxslər cavablandıra bilsin və suallar təkrarlanmasın.
Həssas versiya: https://s3.amazonaws.com/cmsms/downloads/13104/cmsms-2.1.2-install.zip
Sabit (fixed) versiya: https://s3.amazonaws.com/cmsms/downloads/13232/cmsms-2.1.3-install.zip
“NIST” artıq zəifliyin Host başlığında olduğunu müəyyən edib, lakin bu məşq üçün gəlin təsəvvür edək ki, bizdə bu məlumat yoxdur. Kimsə (mən yox) koda edilən dəyişiklikləri müəyyən etmək və təhlil etmək üçün “Patch-Diff” analizi aparıb: https://github.com/hatlesswizard/patch-diff/compare/v2.1.2…v2.1.3 .
Dəyişiklər əsasən class.cms_config.php faylındadırlar.
Əlavə olunan kod:
Code:
private function calculate_request_hostname()
{
if( $_SERVER['HTTP_HOST'] === $_SERVER['SERVER_NAME'] ) return $_SERVER['SERVER_NAME'];
// $_SERVER['HTTP_HOST'] can be spoofed... so if a root_url is not specified
// we determine if the requested host is in a whitelist.
// if all else fails, we use $_SERVER['SERVER_NAME']
$whitelist = (isset($this['host_whitelist'])) ? $this['host_whitelist'] : null;
if( !$whitelist ) return $_SERVER['SERVER_NAME'];
$requested = $_SERVER['HTTP_HOST'];
$out = null;
if( is_callable($whitelist) ) {
$out = call_user_func($whitelist,$requested);
}
else if( is_array($whitelist) ) {
// could use array_search here, but can't rely on the quality of the input (empty strings, whitespace etc).
for( $i = 0, $n = count($whitelist); $i < $n; $i++ ) {
$item = $whitelist[$i];
if( !is_string($item) ) continue;
if( !$item ) continue;
if( strcasecmp($requested,$item) == 0 ) {
$out = $item;
break;
}
}
}
else if( is_string($whitelist) ) {
$whitelist = explode(',',$whitelist);
// could use array_search here, but can't rely on the quality of the input (empty strings, whitespace etc).
for( $i = 0, $n = count($whitelist); $i < $n; $i++ ) {
$item = $whitelist[$i];
if( !is_string($item) ) continue;
$item = strtolower(trim($item));
if( !$item ) continue;
if( strcasecmp($requested,$item) == 0 ) {
$out = $item;
break;
}
}
}
if( !$out ) {
trigger_error('HTTP_HOST attack prevention: The host value of '.$requested.' is not whitelisted. Using '.$_SERVER['SERVER_NAME']);
$out = $_SERVER['SERVER_NAME'];
}
return $out;
}
Silinən kod:
Code:
if( CmsApp::get_instance()->is_https_request() ) $prefix = 'https://';
$str = $prefix.$_SERVER['HTTP_HOST'].$path;
Əvəz olunan kod:
Code:
if( cmsms()->is_https_request() ) $prefix = 'https://';
$str = $prefix.$this->calculate_request_hostname().$path;
calculate_request_hostname funksiyası 2.1.3-ə əlavə edildi (yuxarıdakı böyük kod)
Yuxarıda öyrəndik ki, $_SERVER[‘HTTP_HOST’] dəyəri Host başlığından götürülüb, bu kodda $_SERVER[‘SERVER_NAME’] istifadə olunur. HTTP_HOST dəyərinin SERVER_NAME dəyərinə bərabər olub olmadığını yoxlayır. Əgər onlar bərabərdirsə, o, $_SERVER[‘SERVER_NAME’] qaytarır. Bu, Host başlığının inyeksiyasının qarşısını almaq üçün əsas yoxlamadır. Əgər HTTP_HOST və SERVER_NAME bərabər deyilsə, funksiya icazə verilən host adlarının siyahısının təmin edilib-edilmədiyini yoxlayır. Ağ siyahı $this[‘host_whitelist’] dəyişənində saxlanılır, əgər heç bir ağ siyahı təqdim edilməyibsə ($whitelist null və ya təyin olunmayıb), o, defolt olaraq $_SERVER[‘SERVER_NAME’]-ni qaytarır.
Server adı config.php-də saxlanılır, məsələn:
Code:
<?php
$_SERVER['SERVER_NAME'] = 'exploit.az';
?>
Və sonra həmin konfiqurasiya istifadə edilir:
Code:
<?php
require_once('config.php');
?>
<!DOCTYPE html>
<html>
<head>
<title>Server Name and HTTP Host</title>
</head>
<body>
<h1>Server Name:</h1>
<p><?php echo $_SERVER['SERVER_NAME']; ?></p>
<h1>HTTP Host:</h1>
<p><?php echo $_SERVER['HTTP_HOST']; ?></p>
</body>
</html>
Host başlığının dəyəri fərqli domenə təyin edilsə belə, server adı istifadə olunacaq.
İndi biz əminik ki, zəiflik Host başlığındadır və tərtibatçıların $_SERVER[‘SERVER_NAME’] istifadə etmələrinin səbəbi budur.
İstinadlar: