CVE-2023-38890 – Online Shopping Portal 3.1 Unauthenticated RCE
This vulnerability affects SourceCodester Online Shopping Portal version 3.1.
It results in remote unauthenticated code execution via SQL injection and unrestricted file upload.
1. Reconnaissance & Discovery: The Entry Point (SQL Injection)
My initial target was the administrative interface. As a black-box tester, the first logical step is to test the authentication mechanism for weaknesses.
The Discovery Process
I navigated to /admin/index.php and was presented with a standard login form. Instead of guessing passwords, I tested for SQL Injection (SQLi) by injecting special characters into the username field.
- Attempt 1:
admin' -> Result: Potential error or invalid login.
- Attempt 2:
' OR 1=1-- -> Result: Successful Login.
By inputting ' OR 1=1--, I effectively tricked the database. The query logic was altered to "Select user where username is empty OR 1 equals 1". Since 1=1 is always true, the database returned the first record in the admin table, logging me in as the administrator without a password.
Root Cause Analysis
Upon reviewing the source code in admin/index.php, the vulnerability became obvious:
$username=$_POST['username'];
$ret=mysqli_query($con,"SELECT * FROM admin WHERE username='$username' and password='$password'");

Why this is vulnerable: The developer directly concatenated user input ($username) into the SQL query string. There is no input sanitization, no escaping of special characters, and most importantly, no use of Prepared Statements. This is a textbook SQL Injection vulnerability.
2. Escalation: Hunting for RCE (Unrestricted File Upload)
Having gained administrative access, my goal shifted from access to control (Remote Code Execution). I began mapping the application's features, looking for any functionality that interacts with the file system.
The Discovery Process
I found the "Insert Product" page (admin/insert-product.php). This form allows administrators to upload product images. This is a prime target for malicious file uploads.
I attempted to upload a file named shell.php containing a simple PHP script instead of a valid image (JPG/PNG).
- Result: The application accepted the file without any error.
- Verification: I checked the upload directory (
productimages/{id}/) and found my .php file was stored and executable by the web server.
Root Cause Analysis
The code responsible for handling uploads in admin/insert-product.php is critically flawed:
move_uploaded_file($_FILES["productimage1"]["tmp_name"],"productimages/$productid/".$_FILES["productimage1"]["name"]);

Why this is vulnerable:
- No Extension Validation: The code does not check if the file extension is safe (e.g.,
.jpg, .png). It blindly accepts .php, .exe, etc.
- No MIME Type Check: It does not verify the actual content of the file.
- No Renaming: It saves the file using the original name provided by the user (
$_FILES["..."]["name"]). This allows an attacker to predict the file path easily.
3. Weaponization: Developing the Exploit
With the two vulnerabilities identified, I chained them together to create a fully automated exploit script (exploit.py).
The Exploit Logic
-
Session Initialization: I use requests.session() to maintain cookies, simulating a browser.
-
Authentication Bypass: The script sends a POST request to /admin/ with the payload ' OR 1=1-- a in the username field. This establishes an authenticated administrator session.
-
Payload Generation: I generate a random string for the product name and the shell filename to avoid collisions and detection. The payload is a simple PHP web shell:
<?php if(isset($_GET['cmd'])){ system($_GET['cmd']); } ?>
-
Malicious Upload: The script constructs a multipart/form-data POST request to insert-product.php. It fills in the required product fields and attaches the PHP shell as productimage1.
-
Path Extraction: After the upload, the script searches for the newly created product to find the exact path where the server stored the shell.
-
Execution: Finally, it prints the URL of the uploaded shell. Accessing this URL with ?cmd=whoami executes commands on the server.
4. Remediation: How to Fix
To secure this application, the code must be refactored significantly.
Fixing the SQL Injection
Use Prepared Statements (Parameterized Queries). This ensures that the database treats user input as data, not executable code.
Secure Code Example (admin/index.php):
$stmt = $con->prepare("SELECT id, username, password FROM admin WHERE username=? AND password=?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
// Login successful
}
Fixing the File Upload
Implement strict validation and file renaming.
Secure Code Example (admin/insert-product.php):
$allowed_extensions = array("jpg", "jpeg", "png", "gif");
$filename = $_FILES["productimage1"]["name"];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
// 1. Check Extension
if (!in_array($ext, $allowed_extensions)) {
die("Invalid file type.");
}
// 2. Check MIME Type (Content)
$check = getimagesize($_FILES["productimage1"]["tmp_name"]);
if ($check === false) {
die("File is not an image.");
}
// 3. Rename File (Prevent overwriting and execution)
$new_filename = md5(time() . $filename) . '.' . $ext;
move_uploaded_file($_FILES["productimage1"]["tmp_name"], "productimages/$productid/" . $new_filename);
CVE ID: CVE-2023-38890
Original public disclosure: June 17, 2021 (Exploit-DB)
Researcher: Tağmaç "Tagoletta"
Canonical reference: https://www.exploit-db.com/exploits/50029
CVE-2023-38890 – Online Shopping Portal 3.1 Unauthenticated RCE
Bu zafiyet SourceCodester Online Shopping Portal sürüm 3.1'i etkilemektedir.
SQL enjeksiyonu ve kısıtlanmamış dosya yükleme yoluyla uzaktan kimlik doğrulaması olmadan kod yürütülmesine (RCE) neden olur.
1. Keşif ve Tespit: Giriş Noktası (SQL Enjeksiyonu)
Hedefim yönetim paneliydi. Bir "Black Hat" bakış açısıyla, ilk adımım her zaman kimlik doğrulama mekanizmasını zorlamak ve zayıflık aramaktır.
Keşif Süreci
/admin/index.php adresine gittim ve standart bir giriş formuyla karşılaştım. Şifre tahmin etmekle vakit kaybetmek yerine, username alanına özel karakterler girerek SQL Enjeksiyonu (SQLi) testi yaptım.
- Deneme 1:
admin' -> Sonuç: Hata veya geçersiz giriş.
- Deneme 2:
' OR 1=1-- -> Sonuç: Başarılı Giriş.
' OR 1=1-- payload'ını girerek veritabanını kandırdım. Sorgu mantığını "Kullanıcı adı boş olsun VEYA 1 eşittir 1 olsun" şeklinde değiştirdim. 1=1 her zaman doğru olduğu için, veritabanı admin tablosundaki ilk kaydı döndürdü ve şifre girmeden yönetici olarak içeri girdim.
Kök Neden Analizi (Root Cause)
admin/index.php kaynak kodunu incelediğimde zafiyetin nedeni barizdi:
$username=$_POST['username'];
$ret=mysqli_query($con,"SELECT * FROM admin WHERE username='$username' and password='$password'");

Neden Zafiyetli: Geliştirici, kullanıcıdan gelen $username verisini doğrudan SQL sorgusuna yapıştırmış. Hiçbir temizleme (sanitization) yok, özel karakterler kaçış dizisine alınmamış ve en önemlisi Hazırlanmış İfadeler (Prepared Statements) kullanılmamış. Bu, ders kitaplarına konu olacak türden bir SQL Enjeksiyonudur.
2. Yetki Yükseltme: RCE Avı (Kısıtlamasız Dosya Yükleme)
Yönetici erişimini elde ettikten sonra hedefim sadece erişim değil, sunucuyu tamamen kontrol etmekti (Uzaktan Kod Çalıştırma - RCE). Uygulamanın dosya sistemiyle etkileşime giren özelliklerini aramaya başladım.
Keşif Süreci
"Ürün Ekle" (Insert Product) sayfasını (admin/insert-product.php) buldum. Bu form, yöneticilerin ürün resimleri yüklemesine izin veriyordu. Bu, zararlı dosya yüklemek için mükemmel bir hedeftir.
Geçerli bir resim (JPG/PNG) yerine, içinde basit bir PHP betiği olan shell.php adında bir dosya yüklemeyi denedim.
- Sonuç: Uygulama dosyayı hiçbir hata vermeden kabul etti.
- Doğrulama: Yükleme dizinini (
productimages/{id}/) kontrol ettim ve .php dosyamın sunucuda saklandığını ve çalıştırılabilir olduğunu gördüm.
Kök Neden Analizi
admin/insert-product.php dosyasındaki yükleme işlemi kodları kritik derecede hatalıydı:
move_uploaded_file($_FILES["productimage1"]["tmp_name"],"productimages/$productid/".$_FILES["productimage1"]["name"]);

Neden Zafiyetli:
- Uzantı Kontrolü Yok: Kod, dosya uzantısının güvenli olup olmadığını (örn.
.jpg, .png) kontrol etmiyor. .php, .exe gibi dosyaları körü körüne kabul ediyor.
- MIME Türü Kontrolü Yok: Dosyanın gerçek içeriğini doğrulamıyor.
- Yeniden Adlandırma Yok: Dosyayı kullanıcının verdiği orijinal isimle kaydediyor (
$_FILES["..."]["name"]). Bu, saldırganın dosya yolunu kolayca tahmin etmesini sağlıyor.
3. Silahlandırma: Exploit Geliştirme
Tespit ettiğim bu iki zafiyeti birleştirerek tam otomatik bir saldırı aracı (exploit.py) geliştirdim.
Exploit Mantığı
-
Oturum Başlatma: requests.session() kullanarak çerezleri (cookies) koruyorum, böylece bir tarayıcıyı simüle ediyorum.
-
Kimlik Doğrulama Atlatma: /admin/ adresine username alanında ' OR 1=1-- a payload'ı ile bir POST isteği gönderiyorum. Bu, yetkili bir yönetici oturumu açmamı sağlıyor.
-
Payload Oluşturma: Tespit edilmemek ve çakışmaları önlemek için rastgele bir ürün adı ve shell dosya adı üretiyorum. Payload basit bir PHP web shell'idir:
<?php if(isset($_GET['cmd'])){ system($_GET['cmd']); } ?>
-
Zararlı Yükleme: insert-product.php sayfasına multipart/form-data formatında bir POST isteği hazırlıyorum. Gerekli ürün bilgilerini dolduruyorum ve productimage1 alanına PHP shell dosyamı ekliyorum.
-
Yol Tespiti: Yükleme işleminden sonra, sunucunun shell dosyasını tam olarak nereye kaydettiğini bulmak için yeni oluşturduğum ürünü aratıyorum.
-
Çalıştırma: Son olarak, yüklenen shell'in URL'ini ekrana yazdırıyorum. Bu URL'e ?cmd=whoami parametresiyle gidildiğinde sunucuda komut çalıştırılmış oluyor.
4. İyileştirme: Nasıl Kapatılır?
Bu uygulamayı güvenli hale getirmek için kodun önemli ölçüde yeniden düzenlenmesi gerekir.
SQL Enjeksiyonunu Düzeltme
Hazırlanmış İfadeler (Prepared Statements) kullanılmalıdır. Bu, veritabanının kullanıcı girdisini kod olarak değil, sadece veri olarak işlemesini sağlar.
Güvenli Kod Örneği (admin/index.php):
$stmt = $con->prepare("SELECT id, username, password FROM admin WHERE username=? AND password=?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
// Giriş başarılı
}
Dosya Yükleme Zafiyetini Düzeltme
Sıkı bir doğrulama ve dosya yeniden adlandırma işlemi uygulanmalıdır.
Güvenli Kod Örneği (admin/insert-product.php):
$allowed_extensions = array("jpg", "jpeg", "png", "gif");
$filename = $_FILES["productimage1"]["name"];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
// 1. Uzantı Kontrolü
if (!in_array($ext, $allowed_extensions)) {
die("Geçersiz dosya türü.");
}
// 2. MIME Türü (İçerik) Kontrolü
$check = getimagesize($_FILES["productimage1"]["tmp_name"]);
if ($check === false) {
die("Dosya bir resim değil.");
}
// 3. Yeniden Adlandırma (Üzerine yazmayı ve çalıştırmayı önleme)
$new_filename = md5(time() . $filename) . '.' . $ext;
move_uploaded_file($_FILES["productimage1"]["tmp_name"], "productimages/$productid/" . $new_filename);