August 14, 2026
PicoCTF 3v@1 Writeup
Merhaba, bugün 3v@1 adlı challenge’ın çözümünü yapacağım. Öncelikle verdiği yazıyı okuyorum.
By Akcaysalih
3 min read
Yazıda bir borç hesaplayıcısı olduğunu ve bu sitenin borç hesaplama konusunda yardımcı olduğunu söylüyor. Borcu hesaplamak için bir eval() fonksiyonu kullanılıyormuş. Bundan yararlanarak flag'e ulaşmamız bekleniyor. eval() içine yazılanları çalıştıran bir fonksiyon olduğu için, bu açığı doğrulamak adına 2+2 yazıyorum.
Enter'ladığımda 4 dönüyor.
Bu, eval() fonksiyonundan kaynaklı bir code execution açığının varlığını kanıtlıyor. Ancak RCE yapabilmemiz için hangi dilde yazıldığını bilmemiz gerekiyor. Bunu, bazı dillere ait olup diğerlerinde hata verecek ifadelerle yapabiliriz. Python için olanı 'a'*3 gibi bir şey olabilir. Çünkü bu, PHP ve JavaScript gibi diğer dillerde string'i integer'a dönüştürme gibi algılanır, ama Python'da aaa gibi bir sonuç vermesi gerekiyor.
Kodun Python ile yazıldığını öğrenmiş oluyoruz. Hemen eval() fonksiyonunda Python dili için kullanılan RCE payload'larını araştırıyorum.
Bu payload temel olarak os'e bağlanıp system komutuyla içeride komut çalıştırmayı sağlıyor. Payload'ı deniyorum.
Bir blacklist var; arka planda çalışan os yazmamız yasaklanmış. Bunu atlatmanın kolay yolları var, yazıyı parçalamak gibi.
Ve çalıştı. Result 0 dönme sebebi, sonucu ekrana yansıtması için bir komut girmedik. Dolayısıyla ekrana, komutun çalıştığına dair dönen 0'ı döndürüyor. Bunu aşmak için, yazdığım komutun sonucunu bana aktaran bir komut yazmam gerekiyor. system komutu yazdıklarımızı sistem terminalinde çalıştırsa da o sonuç terminalde kalıyor; o yüzden onun yerine, yine çalıştıran ama çıktıyı aktarmamıza yarayan popen komutunu kullanacağım. Bunun sonucunu bize aktarması için de .read().
Payload artık böyle görünüyor, çalıştırıyorum..
Ve id'leri alıyorum. Artık komut çalıştırabildiğime göre, flag'in olduğu dosyayı aramalıyım.
Sistemdeki dosyaları aramak adına id kısmını ls ile değiştirmiştim, ama ls de blacklist'e dahilmiş; onu da aynı os gibi bölerek kullanacağım.
Çalıştırıyorum.
Çalıştı, birkaç adet dizin görüyoruz ama bunların hiçbiri flag'e benzemiyor. Bu yüzden ls'i kök dizinde çalıştırmayı deneyeceğim.
Hata veriyor; neyin blacklist'te olduğunu söylemiyor ama muhtemelen / filtreleniyor. Bunu aşmak oldukça basit: Python'da chr(47), /'ı temsil ediyor. / yerine bunu yazacağım.
flag.txt adlı dosyayı gördük. Şimdi flag'i okumak için cat komutunu kullanacağım.
Yine bir hataya çarpıyorum. Bunu aşmak için cat'i böleceğim. Yine deniyorum ve yine bir hata aldım.
Ve yine bir hata aldım. Neyden dolayı olduğunu söylemiyor; ancak yeni yazdığım şeyleri silerek denediğimde, uzantıların sebep olduğunu görüyorum.Filtrenin tam olarak uzantıyı mı yoksa neyi filtrelediğini görmek için bir test yapıyorum ve şunu görüyorum: bu yazdığım filtreden geçiyor. Filtre, uzantı olarak 3 veya daha az karakter ise kabul etmiyor.
Bunu aşmak için . karakterini başka bir formuyla yazabiliriz, aynı / için yaptığımız gibi. Python chr(46)'yı . gibi yorumluyor, ama filtre muhtemelen doğrudan . yazılması üzerine kurulu olduğu için, bu filtreyi bypass etmemizi sağlayacak.
Çalıştırıyorum.
Ve böylece bütün filtreleri aşıp, eval()'in içine yazılan kodları doğrudan çalıştırması sayesinde flag'e ulaşıyorum. Basit blacklist mantığı bu tarz bir durum için engel oluşturmuyor, çünkü chr() ile kolayca aşılabiliyor.
Bu zafiyetin temel sebebi, kullanıcı girdisini eval() gibi doğrudan kod çalıştıran bir fonksiyonun içine yazmak ve daha güvenli bir alternatif kullanmamaktı. Bu, filtrelerle engellenmeye çalışılmış ama kolayca aşılabilir olduğu için, en sağlıklı yol eval()'in daha güvenli bir alternatifini kullanmaktır.
Okuduğunuz için teşekkür ederim.