Meraklısı için CVE-2019-9787 Wordpress XSS to RCE Zafiyeti
İnternetin popüler blog yazılımı Wordpress'te önemli bir güvenlik açığı daha 5.1.1 versiyonu ile düzeltildi. Zafiyetin detaylarına yazımızda yer verdik.
Statik kod analiz alanında çalışmalarını sürdüren RIPS firması 13 Mart tarihinde Wordpress'in 5.1.1 öncesi sürümlerini etkileyen XSS zafiyetinin ayrıntılarını yayınladı.
Zafiyet çeşitli kanallarda farklı şekilde yer aldı. Kimi CSRF olarak nitelerken kimi de zafiyetin adını doğru bir biçimde koyarak bunun bir XSS zafiyeti olduğu tespitini yaptılar.
Bu yazımızda CVE-2019-9887 zafiyet numarası ile numaralanan bu zafiyetin ayrıntılarına değineceğiz.
Wordpress'in yorum alanında CSRF'i engelleyecek bir nonce değeri kullanılmıyor. Yorum satırlarında 2009 tarihinden beri bilinen bu nonce yoksunluğu aslında bir tasarımın sonucu. CSRF hakkında ayrıntılı yazıya Netsparker Blogu'ndan erişebilirsiniz.
Peki neden bu alanda CSRF saldırılarını engelleyecek bir nonce yok, Wordpress'in tasarım konusundaki hangi kararı böyle bir güvenlik tedbirinin implementasyonunu engelledi?
Wordpress'in pingback ve trackback özellikleri!
Wordpress'in pingback özelliği sizin blog postunuza, başka bir sitedeki Wordpress post'undan bir atıf yapıldığında, bu atıfın sonucu olarak bir yorum bırakıyor. Bu özellik sayesinde Wordpress blog siteleri bugün SEO dünyasında backlink olarak bilinen özellikle arama motoru sonuçlarında ratinglerini arttırmış oluyorlar.
Şayet blog postunuzdan Wordpress kurulu olmayan başka bir siteyi haberdar etmek istiyorsanız o takdirde trackback özelliğini kullanabilirsiniz.
Pingback özelliğinin çalışabilmesi için CSRF'i engelleyebilecek bir nonce değerinin bu fonksiyonun zorunlu bir parçası olmaması gerekiyor.
Admin hesabı ile oturum açmış bir kullanıcı saldırgan kontrolündeki bir siteye girmeye ikna edildiğinde, saldırgan site browserdaki admin hesabı ile ilintili oturumu kullanarak admin adına bloga bir yorum gönderebilir.
Wordpress teorik olarak wp_unfiltered_html_comment_disabled adındaki bir nonce 'u kullanarak admin oturumundan yapılan yorum isteklerinde bu nonce sağlanmadığında yorum alanına girilecek HTML taglarını temizliyor.
Bu denetim ve temizlik aşamasında yapılan bir yanlışlık RIPS mühendislerinin gözünden kaçmadı ve statik kaynak kod analizinde bu noktayı tespit ettiler.
/wp-includes/comment.php dosyasındaki kod, bu işlemin Wordpress tarafından nasıl gerçekleştiğine dair ayrıntıları gözler önüne seriyor.
if ( current_user_can( 'unfiltered_html' ) ) {
if (! wp_verify_nonce( $_POST['_wp_unfiltered_html_comment'], 'unfiltered-html-comment' )) {
$_POST['comment'] = wp_filter_post_kses($_POST['comment']);
}
} else {
$_POST['comment'] = wp_filter_kses($_POST['comment']);
}
Kod bloğundan anlaşılacağı üzere eğer kullanıcının unfiltered_html yetkisi yoksa doğrudan yorum metni wp_filter_kses isimli fonksiyona gönderiliyor ve yorumdaki HTML elemanları temizleniyor.
Ama konumuz özelinde saldırı isteği admin hesabı ile oturum açılan browser üzerinden yapılacağı ve admin kullanıcısının unfiltered_html yetkisi bulunduğu için yorumlayıcı şart ifadesinin ilk bloğundan devam ediyor.
Burada kod akışı nonce'ın doğrulanmadığı durumda bu defa yorum metnini başka bir fonksiyon olan wp_filter_post_kses fonksiyonuna zararlı olabilecek HTML taglarından temizlenmesi için gönderiyor.
wp_filter_kses ve wp_filter_post_kses fonksiyonlarındaki temel fark, wp_filter_kses fonksiyonunun daha kısıtlayıcı bir liste üzerinden HTML tag temizliği yapması ve sadece href attribute'ü bulunan bir anchor <a> elemanına izin vermesi.
wp_filter_post_kses de zararlı olabilecek HTML taglarını temizliyor, fakat wp_filter_kses fonksiyonuna göre daha az katı.
RIPS mühendislerinin fark ettiği nokta zararlı olabilecek HTML eleman ve attribute'lerinin temizlenme işleminde bir hata yapıldığı yönünde.
Yorum metni temizlendikten sonra SEO amaçlı olarak anchor <a> elemanına ait attribute'ler Wordpress tarafından düzenlenmek isteniyor.
Bu işlem de anchor <a> elemanının attribütelerinin bir ilişkisel array'e aktarılması ile gerçekleştiriliyor.
Örneğin a elemanının sahip olduğu attribute'lerin href="#" title="some link" rel="nofollow"
olduğu varsayılırsa bu string parse edilerek her bir attribute name'i array'in anahtarı olacak şekilde bir diziye dönüştürülüyor.
function wp_rel_nofollow_callback( $matches ) {
$text = $matches[1];
$atts = shortcode_parse_atts($matches[1]);
⋮
Bu dönüşümden sonra örneğin $atts["href"] denildiğine anchor elemanının href özelliğine ulaşılabiliyor.
Bu aşamadan sonra Wordpress yorum metni içerisindeki anchor <a> elemanlarının "rel" attribute'ü içerip içermediğini kontrol ediyor.
Not etmekte fayda var, anchor elemanı için rel attribute'ü sadece yorum yapan kullanıcı admin hesabına sahip ise mümkün olabiliyor. wp_filter_kses fonksiyonu bu attribute'e izin vermiyor. Ancak admin kullanıcısının yorum girdilerini denetleyen wp_filter_post_kses'de bu serbesti mevcut.
if (!empty($atts['rel'])) {
// the processing of the 'rel' attribute happens here
⋮
$text = '';
foreach ($atts as $name => $value) {
$text .= $name . '="' . $value . '" ';
}
}
return '<a ' . $text . ' rel="' . $rel . '">';
}
Bold olarak işaretlenen satırlarda da görüldüğü gibi bir metin olarak tek tek birleştirilen dizi değerleri herhangi bir temizleme işlemine tabi tutulmuyor.
Saldırganın yorum alanına title attribute'ü aşağıdaki şekilde set edilmiş bir anchor <a> elemanı eklediğinde ise tüm şartlar saldırgan lehine dönüyor:
title='XSS " onmouseover=alert(1) id="'
Yorum satırındaki <a title='XSS " onmouseover=evilCode() id=" '>
elemanı fonksiyondan çıktıktan sonra aşağıdaki duruma dönüşüyor:
<a title="XSS " onmouseover=evilCode() id=" ">
Bu zafiyeti istismar etmek isteyen bir saldırganın öncelikle Wordpress 5.1.1 öncesi bir sistemde admin yetkileri ile oturum açmış bir kullanıcıyı kendi hazırladığı ve söz konusu payload'u Wordpress sistemine enjekte edecek sayfayı ziyaret ikna etmesi gerekiyor.
Bu esnada da söz konusu XSS payload'unu enjekte edecek yani zafiyet içeren sisteme yorum gönderecek isteği yapmalı.
<iframe name="hidden_iframe" id="hidden_frame" style="display:none;" >
</iframe>
<form target="hidden_iframe" style="display:none;" action="wp-comments-post.php" method="POST">
<input type="hidden" name="comment" value="<a title='XSS " onmouseover=evilCode() id=" '>"/>
<input type="hidden" name="comment_post_ID" value=1 />
<input type="hidden" name="comment_parent" value=0 />
<input type="submit"/>
</form>
<script>
document.forms[0].submit();
</script>
<script>// <![CDATA[
document.forms[0].submit();
// ]]></script>
Form gönderimi ile beraber top level navigation'a sebebiyet vermemek, saldırıyı gizlemek için Form'un target attribute'ünde belirtildiği üzere form sonucu gelen yanıt display:none stil belirtimi ile gizlenen iframe'e yönlendiriliyor.
Bu aşamadan sonra XSS payload'un yorum olarak eklendiği blog post yine admin browserında görüntülenmeli.
Peki bunu RCE'e çeviren ayrıntı nedir?
Admin paneli üzerinden template dosyalarını editlemek mümkün. Template dosyalarına erişen saldırgan bu kodlara PHP kod enjeksiyonu yapıp bu XSS zafiyetini RCE'e çevirebilir.
Araştırmanın ayrıntıları için lütfen tıklayınız.
Çözüm
Şayet Wordpress blog sistemini kullanıyorsanız en doğru çözüm en hızlı şekilde sistemi zafiyetin fixlendiği 5.1.1 versiyonuna yükseltmek.
Netsparker taradığı web sitelerde Wordpress 5.1.1 sürümünden önceki bir sürüme rastladığı takdirde hem bileşenin Out-of-Date olduğunu haber vermekte, hem de bu ilgili versiyonda bulunan zafiyeti göstermektedir.