WordPress 5.8.3 Nesne Yerleştirme Güvenlik Açığı Nedir ?
Bu yazının yazıldığı sırada, WordPress İnternet'teki web sitelerinin %43'üne güç sağlıyor. Basitliği ve sağlamlığı, milyonlarca kullanıcının bloglarını, e-Ticaret sitelerini, forumlarını veya statik web sitelerini barındırmasına olanak tanır. Kullanıcılarını korumak için geçmişte kod tabanına çeşitli güvenlik güçlendirme mekanizmaları getirilmişti.
WordPress çekirdeğinde yakın zamanda 5.8.3 sürümüyle düzeltilen ilginç bir Nesne Enjeksiyonu güvenlik açığını (CVE-2022-21663) keşfettik . Nesne Enjeksiyonu, saldırganların uygulamaya rastgele türdeki PHP nesnelerini enjekte etmelerine olanak tanıyan ve daha sonra çalışma zamanında uygulamanın mantığını kurcalayabilen bir kod güvenlik açığıdır.
Bu özel güvenlik açığından yararlanmak zor olsa da, bu tür ciddi güvenlik açıklarının hala karmaşık ve güçlendirilmiş kod tabanlarında bulunduğunu gösteriyor. Bu blog yazısında savunmasız kod satırlarını inceliyoruz ve WordPress çekirdeğindeki ilginç bir saldırı yüzeyini ortaya çıkarıyoruz.
Açığı İnceleyelim
Bir WordPress sitesi yüzlerce farklı seçenek tarafından kontrol edilir . Bu seçenekler bir WordPress sitesini yapılandırmak için kullanılır. Temel kodda seçenekler, fonksiyon yardımıyla veritabanından alınır get_option($key) ve fonksiyon yardımıyla güncellenir update_option($key, $value) . Zamanla, bir WordPress sitesinde saklanan seçeneklerin listesi genellikle WordPress eklenti geliştiricileri ve hatta çekirdek geliştiriciler, bir kullanıcı veya hatta yönetici tarafından değiştirilmesi amaçlanmayan dahili verileri seçenek çiftleri olarak depolama eğiliminde oldukça büyür.
Ancak bir WordPress sitesinin yöneticisi olarak, veritabanında depolanan hemen hemen tüm seçenek anahtar/değer çiftlerini listelemek ve değiştirmek mümkündür. Aşağıdaki ekran görüntüsü, bir test örneğinde /wp-admin/options.php adresindeki sayfayı ziyaret ederek elde edilen seçeneklerin listesini gösterir
Yukarıdaki ekran görüntüsündeki bazı seçenek adları, bunlarla ilişkili verilerin dahili süreçlere yönelik olduğunu ve bir yönetici tarafından değiştirilmemesi gerektiğini göstermektedir. Örneğin, seçeneğin değeri active_plugins : ekran görüntüsünde, değeriyle birlikte grileştirilmiş bir alan olarak görüntülenir SERIALIZED_DATA.
WordPress geliştirici referansında belgelendiği gibi update_option($key, $value) işlev, serileştirilebildiği sürece nesneleri, dizileri, tam sayıları, dizeleri ve diğer türleri değer olarak alabilir. Böyle bir durumda, PHP serileştirilmiş bir dize veritabanında saklanır.
WordPress çekirdeği, bir dizenin daha önce seri hale getirilip getirilmediğini kontrol ederek ve öyleyse onu çift seri hale getirerek hiçbir seri durumdan çıkarma saldırısının gerçekleştirilememesini sağlar. Bu, fonksiyon tarafından yapılır maybe_serialize($data) :
wordpress/wp-includes/functions.php
Kod:
597 function maybe_serialize( $data ) {
598 if ( is_array( $data ) || is_object( $data ) ) {
599 return serialize( $data );
600 }
601
602 /*
603 * Double serialization is required for backward compatibility.
604 * See https://core.trac.wordpress.org/ticket/12930
605 * Also the world will end. See WP 3.6.1.
606 */
607 if ( is_serialized( $data, false ) ) {
608 return serialize( $data );
609 }
610
611 return $data;
Fonksiyonun simetrik ikizi maybe_serialize($data) fonksiyondur maybe_unserialize($data) :
wordpress/wp-includes/functions.php
wordpress/wp-includes/functions.php
Kod:
wordpress/wp-includes/functions.php
622 function maybe_unserialize( $data ) {
623 if ( is_serialized( $data ) ) {
624 return @unserialize( trim( $data ) );
625 }
626
627 return $data;
628 }
is_serialized($data) Her iki işlevin de bir dizenin PHP hardened dizeye benzeyip benzemediğini tespit etmek için nasıl kullanıldığına dikkat edin . Bir sonraki bölümde, bu işlevin WordPress çekirdeğinde yanlış kullanılması nedeniyle ortaya çıkan Nesne Enjeksiyonu güvenlik açığı hakkında ayrıntılı bilgi verilmektedir.
WordPress gelen bir isteği her ele aldığında, doğrulama adımlarının bir listesini yürütür. Bu adımlardan biri, WordPress kurulumuyla ilişkili veritabanı sürümünün mevcut kod dosyalarının sürümüyle eşleştiğinden emin olmaktır.
Yayımlanan her yeni WordPress sürümü için en son veritabanı sürümü genel bir değişkende güncellenir:
wordpress/wp-includes/version.php
WordPress gelen bir isteği her ele aldığında, doğrulama adımlarının bir listesini yürütür. Bu adımlardan biri, WordPress kurulumuyla ilişkili veritabanı sürümünün mevcut kod dosyalarının sürümüyle eşleştiğinden emin olmaktır.
Yayımlanan her yeni WordPress sürümü için en son veritabanı sürümü genel bir değişkende güncellenir:
wordpress/wp-includes/version.php
Kod:
18 /**
19 * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
20 *
21 * @global int $wp_db_version
22 */
23 $wp_db_version = 49752;
Yukarıdaki kod gösterilen sürüm, veritabanının tamamen yükseltildiğinde olması gereken sürümdür . Veritabanının istek sırasındaki sürümü bir WordPress seçeneği olarak saklanır. Aşağıdaki kod parçası bu seçeneğin veritabanından nasıl getirildiğini gösterir. Olması gereken sürüme eşit olduğunda herhangi bir işlem yapılmaz ve istek işleme alınır. Sürüm senkronize değilse, aşağıda gösterildiği gibi bir dizi yükseltme komut dosyası çalıştırılır:
Kod:
wordpress/wp-admin/includes/upgrade.php
636 function wp_upgrade() {
637 global $wp_current_db_version, $wp_db_version, $wpdb;
638
639 $wp_current_db_version = __get_option( 'db_version' );
640
641 // We are up to date. Nothing to do.
642 if ( $wp_db_version == $wp_current_db_version ) {
643 return;
644 }
645
646 if ( ! is_blog_installed() ) {
647 return;
648 }
649
// …
654 upgrade_all();
754 // …
755 if ( $wp_current_db_version < 8989 ) {
756 upgrade_270();
757 }
758
759 if ( $wp_current_db_version < 10360 ) {
760 upgrade_280();
761 }
762 // …
Bu davranış ilginçtir, çünkü kötü niyetli bir yönetici $wp_current_db_version kontrol edilebilir bir seçenek olduğundan keyfi bir değere ayarlayabilir. Böylece bir saldırgan, kullanıcılar ve gönderilerle ilişkili seçenek değerleri ve meta veriler gibi kontrol edilebilir veriler üzerinde çalışanlar da dahil olmak üzere herhangi bir veritabanı yükseltme komut dosyasını çalıştırabilir. Bu yetenek, saldırganın WordPress çekirdeğindeki ilginç bir saldırı yüzeyine erişmesini sağlar.
Yürütülen yükseltme komut dosyası upgrade_280() özellikle ilgi çekicidir:
wordpress/wp-admin/includes/upgrade.php
Yürütülen yükseltme komut dosyası upgrade_280() özellikle ilgi çekicidir:
wordpress/wp-admin/includes/upgrade.php
Kod:
wordpress/wp-admin/includes/upgrade.php
1605 function upgrade_280() {
1606 global $wp_current_db_version, $wpdb;
1607
1608 if($wp_current_db_version < 10360 ) {
1609 populate_roles_280();
1610 }
1611 if(is_multisite() ) {
1612 $start = 0;
1613 while($rows = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options ORDER BY option_id LIMIT $start, 20")){
1614 foreach ( $rows as $row ) {
1615 $value = $row->option_value;
1616 if ( ! @unserialize( $value ) ) {
1617 $value = stripslashes( $value );
1618 }
Bu yükseltme betiği, 1613 satırındaki veritabanından seçenekleri getirir ve bunları 1616 satırındaki seri durumdan çıkarmaya çalışır. Dikkat edilmesi gereken önemli ayrıntı, PHP'nin yerleşik unserialize() işlevinin olağan değil, doğrudan kullanılmasıdır maybe_unserialize(). Aşağıdaki paragraflarda bu davranışın neden ilginç olduğu ve nasıl Object Injection güvenlik açığına yol açtığı açıklanacaktır.
Daha önce tartışıldığı gibi, kötü niyetli bir yönetici seçeneklerin değerlerini neredeyse keyfi olarak kontrol edebilir ve bu nedenle serileştirilmiş bir PHP dizesini veritabanına enjekte etmeye çalışabilir. Kısıtlamalardan biri, serileştirilmiş bir PHP dizesi tespit edildiğinde yeniden serileştirilmesi ve dolayısıyla zararsız hale gelmesidir.
Örnek olarak, bir saldırgan bir seçeneğin değerini aşağıdaki hardened dizeye ayarlamaya çalışırsa:
şu şekilde çift hardened yapılır:
Bu çift serileştirmenin sonucu, serileştirilmediğinde yükün zararsız hale gelmesidir, çünkü bu bir dizeyle sonuçlanacaktır.
unserialize() Sonuç olarak, WordPress kodu ile PHP çekirdeğindeki kod arasındaki mantıkta bir fark bulma umuduyla, WordPress çekirdeğinde bir dizenin serileştirilip serileştirilmediğini gerçekten algılayan koda baktık.
unserialize() Bir hatırlatma olarak: PHP'nin işlevi tarafından desteklenen türlerden bazıları şunlardır :
Daha önce tartışıldığı gibi, kötü niyetli bir yönetici seçeneklerin değerlerini neredeyse keyfi olarak kontrol edebilir ve bu nedenle serileştirilmiş bir PHP dizesini veritabanına enjekte etmeye çalışabilir. Kısıtlamalardan biri, serileştirilmiş bir PHP dizesi tespit edildiğinde yeniden serileştirilmesi ve dolayısıyla zararsız hale gelmesidir.
Örnek olarak, bir saldırgan bir seçeneğin değerini aşağıdaki hardened dizeye ayarlamaya çalışırsa:
Kod:
O:20:"SuperDangerousGadget":1:{s:18:"dangerous_property";s:8:"bash ...";}
şu şekilde çift hardened yapılır:
Bu çift serileştirmenin sonucu, serileştirilmediğinde yükün zararsız hale gelmesidir, çünkü bu bir dizeyle sonuçlanacaktır.
unserialize() Sonuç olarak, WordPress kodu ile PHP çekirdeğindeki kod arasındaki mantıkta bir fark bulma umuduyla, WordPress çekirdeğinde bir dizenin serileştirilip serileştirilmediğini gerçekten algılayan koda baktık.
unserialize() Bir hatırlatma olarak: PHP'nin işlevi tarafından desteklenen türlerden bazıları şunlardır :
TYPE | EXAMPLE OF SERIALIZED STRING |
Integer | i:1337; |
Float | d:1337; |
String | s:15:"hack the planet"; |
Object | O:8:"stdClass":0:{} |
Object with custom deserialization function (available in PHP < 7.4) | C:11:"ArrayObject":21:{x:i:0;a:0:{};m:a:0:{}} |
Aşağıda is_serialized($data) WordPress çekirdeğindeki fonksiyondan bir kod alıntısı yer almaktadır. Bu işlev, sağlanan girdinin ilk karakterini, bu dizenin hardened bir PHP dizesi olabileceğini gösteren bir karakter listesiyle karşılaştırır ve ardından daha fazla karşılaştırma yapar. Geçiş durumlarında özel nesnelerin karakterinin nasıl C dikkate alınmadığına dikkat edin:
wordpress/wp-includes/functions.php
wordpress/wp-includes/functions.php
Kod:
677 $token = $data[0];
678 switch ( $token ) {
679 case 's':
680 if ( $strict ) {
681 if ( '"' !== substr( $data, -2, 1 ) ) {
682 return false;
683 }
684 } elseif ( false === strpos( $data, '"' ) ) {
685 return false;
686 }
687 // Or else fall through.
688 case 'a':
689 case 'O':
690 return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
691 case 'b':
692 case 'i':
693 case 'd':
694 $end = $strict ? '$' : '';
695 return (bool) preg_match( "/^{$token}:[0-9.E+-]+;$end/", $data );
696 }
697 return false;
Genellikle bu bir sorun olmaz. Bu işlev, serileştirilmiş dizenin a ile başladığı özel nesneleri kaçırdığından C, bir saldırgan bu tür serileştirilmiş bir PHP dizesini veritabanına enjekte edebilir. Bununla birlikte, işlev dizeyi yalnızca ile hardened bir dize olarak tanındığında maybe_unserialize() PHP'ye ilettiği için , hiçbir zaman hardened olmıcaktır.unserialize()maybe_serialize()
Daha önce açıklanan yükseltme komut dosyasındaki bu simetri maybe_unserialize() bozuldu . maybe_serialize() Dizeyi doğrudan PHP'nin unserialize() işlevine iletir.
Sonuç olarak, bir saldırgan bu güvenlik açığından yararlanmak için aşağıdaki adımları gerçekleştirebilir:
Kötü amaçlı pop zinciri gadget'larını özellikler olarak taşıyan özel bir nesnenin PHP serileştirilmiş dizesini, bir seçenek değeri olarak veritabanına enjekte edin.
maybe_serialize() yükü serileştirilmiş bir dize olarak tanımaz ve onu iki kez serileştirmez.
Güvenlik açığı bulunan yükseltme komut dosyasını tetiklemek için veritabanı sürümü seçeneğini değiştirin
Yükseltme betiği, PHP'nin seri hale getirilmiş dizesini doğrudan öğesine iletir unserialize(); bu dizeyi tanır ve seri durumdan çıkararak pop zincirini tetikler.
Daha önce açıklanan yükseltme komut dosyasındaki bu simetri maybe_unserialize() bozuldu . maybe_serialize() Dizeyi doğrudan PHP'nin unserialize() işlevine iletir.
Sonuç olarak, bir saldırgan bu güvenlik açığından yararlanmak için aşağıdaki adımları gerçekleştirebilir:
Kötü amaçlı pop zinciri gadget'larını özellikler olarak taşıyan özel bir nesnenin PHP serileştirilmiş dizesini, bir seçenek değeri olarak veritabanına enjekte edin.
maybe_serialize() yükü serileştirilmiş bir dize olarak tanımaz ve onu iki kez serileştirmez.
Güvenlik açığı bulunan yükseltme komut dosyasını tetiklemek için veritabanı sürümü seçeneğini değiştirin
Yükseltme betiği, PHP'nin seri hale getirilmiş dizesini doğrudan öğesine iletir unserialize(); bu dizeyi tanır ve seri durumdan çıkararak pop zincirini tetikler.
Yama Güncellemesi
WordPress, bu kod güvenlik açığını WordPress 5.8.3 sürümünde bulunan bir yama işlemiyle düzeltti . Güvenlik açığı, ve arasındaki asimetriyi düzeltmek için güvenlik açığı işlevi kullanılarak giderildi .maybe_unserialize($data)upgrade_280()maybe_serialize($data)unserialize($data)
Konumu Okuduğunuz İçin Teşekkür Ederim.
Yararlandığım Kaynaklar:
1.SonarLint
2.https://developer.wordpress.org/reference/functions/update_option/
3.https://developer.wordpress.org/plugins/settings/options-api/
Konumu Okuduğunuz İçin Teşekkür Ederim.
Yararlandığım Kaynaklar:
1.SonarLint
2.https://developer.wordpress.org/reference/functions/update_option/
3.https://developer.wordpress.org/plugins/settings/options-api/