SQL injection (SQLi) detection and database enumeration tool written in Python.
Vaccine is a command-line tool designed to detect common SQL injection techniques and, where supported, enumerate database metadata and display the resulting database structure.
Disclaimer: Vaccine is intended for authorized security testing, educational labs, and CTF environments. Only use it against systems you own or have explicit permission to test.
Timeline.1.mp4
- Usage
- SQL Injection (SQLi)
- Current limitations
- Architecture
- Detection workflow
- Database enumeration
- JSON output
- Testing
- Tester
- SQL Injection reference
- Legal notice
Clone the repository and install the project's Python dependencies:
git clone <repository-url>
cd vaccine
python -m venv venv
. venv/bin/activate
pip install -r requirements.txtThen run the CLI according to the project's entry point.
Usage:
main.py [-h] [-V VIEW] [-X {GET,POST,PATCH,PUT,DELETE}] [-o OUTPUT] [-D] [-A AGENT] [url]
positional arguments:
url Target URL with its parameters. All methods need their data given in the form of valid parameters and values.
Example: http://localhost:8080/user.php?id=1
options:
-h, --help show this help message and exit
-V, --view VIEW View the formatted results with the given result ID
-X, --method {GET,POST,PATCH,PUT,DELETE}
HTTP method
-o, --output OUTPUT Output file
-D, --debug Enable debug mode
-A, --agent AGENT Custom User-Agent
Vaccine supports a view mode for inspecting previously stored results without running another scan:
Use:
python main.py -V <result-id>View mode displays the formatted database enumeration results and can be used to inspect the database structure, including:
Database
└── Tables
└── Columns
├── Data type
├── Character maximum length
└── Values
This is useful when you only want to inspect the enumerated tables and columns from a previously stored result.
Example vulnerable query:
SELECT first_name, last_name
FROM users
WHERE id = <input>;Vaccine supports several SQL injection techniques:
-
Boolean-based SQLi: injects a true/false condition into the query and infers information from differences in the application's responses.
-
UNION-based SQLi: uses
UNION SELECTto retrieve additional data when the original query is compatible with a UNION query. -
Time-based SQLi: uses conditional database delays and infers information from differences in response times.
-
Error-based SQLi: relies on database error messages returned by the application to detect injection and identify the database engine.
-
No authentication/login handling.
-
Designed for GET, POST, PUT, PATCH, and DELETE parameters.
-
Database enumeration currently supports database-engine-specific metadata and extraction logic.
-
Enumeration capabilities depend on the privileges available to the database user.
-
Time-based detection is sensitive to network and server latency.
-
Some database data types are currently unsupported for value extraction, such as floating-point and decimal types.
User
│
▼
CLI Parser
│
▼
Injection Engine
| │
| ├── Error Test ─────┐
| | | ──▶ Target Analyser (detect context, column count)
| ├── Union Test ─────┘
| |
| ├── Boolean Test
| |
| └── Time Test
│
▼
Response Analyzer
│
▼
DB Fingerprinter ──┐
| | Union
| ▼ |
| Injection Engine ────▶ Get databases
| | |
| ┌── ──┐ +-- get tables from information_schema.tables
| | | | WHERE table_schema = <that database>
| Bool Time |
| +-- get columns from information_schema.columns
| WHERE table_schema = <that database>
▼
Table Display of the Database
│
▼
Storage (JSON)
Vaccine first extracts the parameters supplied to the target endpoint and tests each parameter individually.
For a detected injection point, the tool attempts to determine the SQL context, for example whether the parameter is inserted into a quoted or unquoted expression.
The detected context is then used by subsequent injection tests.
For UNION-based injection, Vaccine determines the number of columns expected by the original query before attempting further enumeration.
When the required injection technique is available, Vaccine can enumerate database metadata using database-engine-specific metadata queries.
When the application does not directly return the value being queried, Vaccine can infer it one character at a time using blind SQL injection.
For each character, the tool converts the character to its ASCII numeric value and uses a binary search over the printable ASCII range (32 to 126). Instead of testing every possible character, it asks questions such as whether the ASCII value is greater than the current midpoint.
For example, if the character is A, its ASCII value is 65. The search can test progressively smaller ranges:
32 ─────────────────────────────── 126
│
> 79 ? → false
32 ───────────── 79
│
> 55 ? → true
│
...
│
65
With Boolean-based SQLi, the result of each comparison is inferred from a difference in the HTTP response. A true condition produces one observable response, while a false condition produces another. Binary search uses that true/false result to select the next half of the ASCII range.
With Time-based SQLi, the same binary-search algorithm is used, but the true/false result is communicated through response time instead of response content. A condition can trigger a database delay when true and avoid the delay when false. A significantly slower response therefore represents true, while a normal response represents false.
The process is repeated for every character position until the complete value has been reconstructed.
For supported integer types, Vaccine can determine numeric values directly using boolean or time-based binary search rather than converting the value to individual characters.
Current integer types include:
TINYINT
SMALLINT
MEDIUMINT / INT
BIGINT
depending on the database engine.
Floating-point and decimal types are currently treated as unsupported because reliable value extraction requires additional handling for decimal precision and floating-point representation.
When database enumeration is possible, Vaccine can retrieve:
Databases
│
├── Tables
│ │
│ └── Columns
│ │
│ ├── Data type
│ ├── Character maximum length
│ └── Values
System schemas such as the following are skipped during normal database enumeration:
information_schema
mysql
performance_schema
sys
Table and column metadata can be retrieved from:
information_schema.tablesand:
information_schema.columnsThe extracted column metadata includes the database's declared data type and, where applicable, its character maximum length.
Vaccine groups database column types into categories used by the extraction engine.
Example:
STRING
├── CHAR
├── VARCHAR
├── NCHAR
├── NVARCHAR
├── TEXT
└── NTEXT
INTEGER
├── TINYINT
├── SMALLINT
├── MEDIUMINT
├── INT
└── BIGINT
BOOLEAN
└── BOOLEAN / BIT
UNSUPPORTED
├── FLOAT
└── DECIMAL
Database engines may expose type names differently. Declared lengths such as:
VARCHAR(50)
CHAR(10)
DECIMAL(10,2)
are normalized so that the base data type can be compared against the supported type lists.
For example:
VARCHAR(50) → VARCHAR
CHAR(10) → CHAR
DECIMAL(10,2) → DECIMAL
Large or legacy text types may require database-specific conversion when determining the actual length of a value.
For example, SQL Server's legacy TEXT and NTEXT types require conversion to their corresponding VARCHAR(MAX) or NVARCHAR(MAX) types when certain string-length operations are performed.
Results can be stored in JSON for later inspection.
The database dump follows a structure similar to:
{
"database_name": {
"table_name": {
"column_name": {
"data_type": "varchar",
"character_maximum_length": 100,
"values": [
"admin",
"root"
]
}
}
}
}The stored result can also contain scan metadata such as the target URL and HTTP method.
For example:
{
"url": "http://localhost:8080/user.php?id=1",
"method": "GET",
"results": {
"database_name": {
"table_name": {
"column_name": {
"data_type": "varchar",
"character_maximum_length": 100,
"values": [
"admin",
"root"
]
}
}
}
}
}The -V / --view option can be used to display a previously stored result without performing a new scan.
For testing, it is recommended to use intentionally vulnerable applications in an isolated environment.
The project includes an intentionally vulnerable SQL injection lab in lab/.
The lab contains testing environments for multiple database engines:
lab/
├── MariaDB
├── SQLite
├── MSSQL
└── web application
The normal Compose file starts the web application together with the MariaDB and SQLite testing environments:
cd lab
docker compose up --buildThe lab is available at:
http://localhost:8080
Open http://localhost:8080/ to access the test page. It provides vulnerable parameters for testing different SQL injection contexts and techniques.
To stop the lab:
docker compose downThe lab is intended for local development and testing. Do not expose it to untrusted networks.
MSSQL is provided through a separate Compose file:
docker compose -f docker-compose.mssql.yml up --buildMSSQL is separated into its own Compose configuration because the Microsoft SQL Server Docker image is significantly larger and takes considerably longer to download.
The MSSQL Compose configuration still includes the other services required by the lab. Therefore, using:
docker compose -f docker-compose.mssql.yml up --buildstarts the complete lab environment, including:
Web application
│
├── MariaDB
│
├── SQLite
│
└── MSSQL
The difference is that this configuration additionally starts SQL Server.
The SQL Server container exposes port 1433.
The MSSQL initialization container waits for SQL Server to become available and then executes the MSSQL initialization SQL script.
Useful commands:
Check the running containers:
sudo docker compose -f docker-compose.mssql.yml psView MSSQL logs:
sudo docker compose -f docker-compose.mssql.yml logs mssqlView the initialization logs:
sudo docker compose -f docker-compose.mssql.yml logs mssql-initConnect to the MSSQL database from the container:
sudo docker compose -f docker-compose.mssql.yml exec mssql \
/opt/mssql-tools18/bin/sqlcmd \
-S localhost \
-U sa \
-P 'VaccineLab123!' \
-C \
-d VaccineLabRun a query directly:
sudo docker compose -f docker-compose.mssql.yml exec mssql \
/opt/mssql-tools18/bin/sqlcmd \
-S localhost \
-U sa \
-P 'VaccineLab123!' \
-C \
-d VaccineLab \
-Q "SELECT * FROM users"Check the character length of the MSSQL TEXT test value:
sudo docker compose -f docker-compose.mssql.yml exec mssql \
/opt/mssql-tools18/bin/sqlcmd \
-S localhost \
-U sa \
-P 'VaccineLab123!' \
-C \
-d VaccineLab \
-Q "SELECT LEN(
CAST(
(
SELECT [long_text]
FROM [VaccineLab].[dbo].[users]
ORDER BY [id]
OFFSET 0 ROWS FETCH NEXT 1 ROW ONLY
)
AS VARCHAR(MAX)
)
);"For SQL Server TEXT and NTEXT values, string functions require an explicit conversion.
For TEXT:
SELECT LEN(
CAST(
(
SELECT [long_text]
FROM [VaccineLab].[dbo].[users]
ORDER BY [id]
OFFSET 0 ROWS FETCH NEXT 1 ROW ONLY
)
AS VARCHAR(MAX)
)
);For NTEXT, use NVARCHAR(MAX):
SELECT LEN(
CAST(
(
SELECT [unicode_long_text]
FROM [VaccineLab].[dbo].[users]
ORDER BY [id]
OFFSET 0 ROWS FETCH NEXT 1 ROW ONLY
)
AS NVARCHAR(MAX)
)
);The MSSQL lab uses the id column for deterministic row ordering. Legacy SQL Server TEXT and NTEXT columns cannot be directly sorted with ORDER BY, so they should not be used as the pagination/order column.
Stop the MSSQL lab with:
sudo docker compose -f docker-compose.mssql.yml downIf the lab is intentionally disposable and the database needs to be recreated, remove the relevant database volume/container data before starting it again.
SQLite does not require a separate database container.
The project includes an initialization script:
lab/init-sqlite.php
which creates the SQLite test tables and inserts the lab data.
Run it manually with:
sudo docker compose exec web php /var/www/html/init-sqlite.phpThe SQLite initialization script can be used to reset the SQLite test database when the existing tables are dropped before initialization.
To inspect the SQLite database schema from the web container:
sudo docker compose exec web php -r '
require "/var/www/html/config/config-sqlite.php";
foreach ($conn->query("SELECT name, sql FROM sqlite_master WHERE type=\"table\"") as $row) {
print_r($row);
}
'To inspect the columns of the users table:
sudo docker compose exec web php -r '
require "/var/www/html/config/config-sqlite.php";
foreach ($conn->query("PRAGMA table_info(users)") as $row) {
echo $row["name"] . PHP_EOL;
}
'To find SQLite database files inside the web container:
sudo docker compose exec web \
find /var/www/html -type f \( -name "*.db" -o -name "*.sqlite" -o -name "*.sqlite3" \) -lsIf the SQLite database is only being used for testing, the database file can be removed and recreated by running the initialization script again.
The project also includes a simple Bash script (test.sh) for running multiple test targets against Vaccine.
Run it with:
chmod +x tester.sh
./tester.shAdd or uncomment entries in TESTS to test different endpoints and HTTP methods.
The following reference is useful when developing and testing MySQL/MariaDB SQL injection functionality:
MySQL SQL Injection Cheat Sheet — Pentestmonkey
Vaccine is a security-testing tool. Do not use it against systems without authorization.
The author is not responsible for damage, data loss, service disruption, or unauthorized access resulting from misuse of this software.
