Wapiti on Ubuntu 24.04 LTS on Azure
This image ships Wapiti on Ubuntu 24.04 LTS, ready to scan a running web application you own for vulnerabilities from the first boot of every deployed virtual machine. Wapiti is installed from PyPI, pinned to a known release inside an isolated virtualenv, with a simple wapiti command on the path.
Wapiti is an open source dynamic application security testing (DAST) tool: a black box web vulnerability scanner. Point it at the URL of an application you own or are authorised to test and it crawls the pages and forms, then injects crafted payloads to surface real vulnerabilities: SQL injection, cross site scripting, command and code injection, path traversal, XML external entity, server side request forgery, open redirects, exposed backup files and more, reporting each finding with the exact request that triggered it.
Only scan systems you are authorised to test
Read this first. Wapiti is an offensive security tool: it actively attacks the target you point it at. Running it against a host you do not own or have written permission to test is illegal in most jurisdictions and is a breach of the acceptable use policy of every cloud provider, including Azure.
This image ships safe by default. It scans nothing on its own. There is no default target, the scheduled scan is empty until you add one, and the only scan this guide runs is against a throwaway target the commands stand up on the machine's own loopback address (127.0.0.1). Only ever add your own applications, or applications you have explicit authorisation to test, to the scheduled scan.
What this appliance does and does not do
What Wapiti is good at. Wapiti answers one question with authority: does my deployed, running web application expose a vulnerability that an attacker could reach over HTTP. Because it drives the live application from the outside, it catches issues that only appear in the deployed system, such as a reflected cross site scripting flaw or an injectable parameter, complementing static code scanning rather than replacing it.
What Wapiti is not. Wapiti is an on demand, black box scanner. This image does not include, and Wapiti itself is not:
- a static code scanner: it tests a running application over HTTP, it does not read your source code,
- a web application firewall or a runtime protection agent: it finds flaws, it does not block attacks,
- a central console, a SIEM or an agent that reports to a server,
- a remediation engine: it reports findings, the fix to the target application is yours to make,
- a web interface of any kind.
Wapiti tells you that a running application exposes a flaw, and shows you the request that proves it. Fixing the application is your work, and this guide shows you the workflow.
What makes this image different
It is ready to run and honest about having no credential. Wapiti has no user database, no network service and no credential of any kind. There is nothing here to rotate and nothing to leak. Administration is over SSH with your own key.
The release is pinned in an isolated virtualenv. Wapiti and its Python dependencies are installed into a dedicated virtualenv, pinned to a known release and scanned clean of fixable vulnerabilities before the image is sealed, so what you run is reproducible rather than whatever floated to the top of an index on build day.
A scheduled scan is already wired, and ships with no target. A systemd timer is ready to scan the targets you authorise, and logs its reports, so you get continuous coverage of applications you own with almost nothing to configure. It scans nothing until you add a target.
The image fails closed. The scheduled scan will not run unless first boot has completed and the scanner actually executes. Two independent safeguards enforce that on every run, not merely the first.
1. Deploy the virtual machine
Deploy the image from the Azure Marketplace. The defaults in this guide assume:
- Size:
Standard_B2s(2 vCPU, 4 GB) is sufficient and is what this image is validated on. Wapiti is a command line tool; size up only for scanning very large applications. - Inbound ports: SSH (22) only. Wapiti opens no network port of its own, so nothing else needs to be open.
- Authentication: your own SSH public key.
2. Connect to the virtual machine
ssh azureuser@<vm-ip>
On first boot the machine self verifies the scanner and records an instance note. That takes a moment. If you connect immediately and first boot is not finished yet, that is first boot still working, and section 9 shows how to watch it.
3. Confirm Wapiti is installed
wapiti --version
sudo test -f /var/lib/cloudimg/wapiti-firstboot.done && echo "firstboot sentinel present"
systemctl is-active wapiti-firstboot.service
You should see the pinned Wapiti version, the first boot sentinel present, and a first boot service that is active. Wapiti lives in an isolated virtualenv at /opt/wapiti/venv; the wapiti command on your path is a thin wrapper around it.

4. See the attack modules it loads
Wapiti runs a set of attack modules, each targeting a class of vulnerability. List them to confirm the module set loads:
wapiti --list-modules
You should see the modules Wapiti can run, including sql (SQL injection), xss (reflected cross site scripting), exec (command and code execution), xxe (XML external entity), ssrf (server side request forgery), redirect (open redirect), file (path traversal and file disclosure) and more. The modules marked used by default are the ones a plain scan runs; you can select a specific set with -m.

5. Prove it actually detects a vulnerability
Do not take "the scanner ran" as proof that detection works. Prove it. This stands up a tiny, deliberately vulnerable web page on the machine's own loopback address that reflects a URL parameter back into the page without escaping it, a textbook reflected cross site scripting flaw, then scans it and confirms Wapiti reports the finding.
cat > /tmp/demo-target.py <<'PY'
import sys, html
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
class H(BaseHTTPRequestHandler):
def log_message(self, *a): pass
def do_GET(self):
q = parse_qs(urlparse(self.path).query); name = q.get("name", [""])[0]
body = (f"<html><body><p>Hello {name}</p>"
"<form action='/' method='get'><input name='name'></form></body></html>").encode()
self.send_response(200); self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(body))); self.end_headers()
try: self.wfile.write(body)
except Exception: pass
HTTPServer(("127.0.0.1", 8231), H).serve_forever()
PY
python3 /tmp/demo-target.py & SRV=$!; echo "demo target running on 127.0.0.1:8231 (loopback only)"
sleep 1
wapiti -u "http://127.0.0.1:8231/?name=demo" -m xss --scope url --flush-session -f json -o /tmp/demo-report.json
python3 -c "import json; d=json.load(open('/tmp/demo-report.json')); v={k:x for k,x in d['vulnerabilities'].items() if x}; print('findings:', {k: len(x) for k,x in v.items()}); [print(' ', x[0]['info'], '(parameter:', x[0]['parameter'], ')') for x in v.values()]"
kill "$SRV" 2>/dev/null; rm -f /tmp/demo-target.py /tmp/demo-report.json
You should see Wapiti report a Reflected Cross Site Scripting finding on the name parameter. That is the whole product: it found, from the outside and over HTTP, a real flaw in the running application, and named the parameter that carries it.

6. Scan your own application
To scan an application you own or are authorised to test, point Wapiti at its URL and write a report. A full scan runs the default module set and produces an HTML report you can open in a browser:
# Replace the URL with YOUR OWN application, or one you are authorised to test.
wapiti -u https://your-app.example/ -f html -o ~/wapiti-report.html
Useful flags: -m xss,sql,exec restricts to a chosen set of modules; --scope domain lets the crawler follow links across the whole domain rather than a single page; -d 3 sets how deep the crawler follows links; --flush-session starts a fresh scan rather than resuming. Run wapiti --help for the full set. Wapiti keeps its scan session under ~/.wapiti, so a long scan can be resumed if interrupted.
7. The scheduled scan
A systemd timer can scan a set of target URLs you configure and writes each report to /var/log/wapiti/, so you have continuous coverage of applications you own. It ships with no target configured, so it scans nothing until you add one.
systemctl is-active wapiti-scan.timer
systemctl list-timers wapiti-scan.timer --no-pager | sed -n '1,3p'
sudo sed -n '1,12p' /etc/wapiti/targets.conf

To enable the scheduled scan, add one target URL per line to /etc/wapiti/targets.conf, using only hosts you own or are authorised to test. To run the scheduled scan immediately rather than waiting for the timer, and then read the tail of its log:
# Add ONLY hosts you are authorised to test, one URL per line.
echo "https://your-app.example/" | sudo tee -a /etc/wapiti/targets.conf
sudo systemctl start wapiti-scan.service
sudo tail -n 20 /var/log/wapiti/scan.log
8. First boot and troubleshooting
First boot is handled by wapiti-firstboot.service, which self verifies the scanner on this machine and writes the instance note at /root/wapiti-instance.txt. To watch or re-run it:
systemctl status wapiti-firstboot.service --no-pager | sed -n '1,8p'
sudo sed -n '1,14p' /root/wapiti-instance.txt
If first boot did not complete (for example the machine lost power mid first boot), re-run it:
# Re-run first boot if it did not complete.
sudo systemctl restart wapiti-firstboot.service
The scheduled scan refuses to run until first boot has completed and the scanner executes, so a machine that has not finished first boot will never present a scan that silently ran nothing.
9. Licensing
Wapiti is licensed under the GNU General Public License, version 2, and is installed unmodified from PyPI (the wapiti3 distribution). The licence text is shipped on the image at /usr/share/doc/cloudimg/WAPITI-LICENSE.txt, and because the GPL is a copyleft licence, a written offer for the corresponding source, along with the exact command to fetch it, is shipped at /usr/share/doc/cloudimg/WAPITI-GPL-2-WRITTEN-OFFER.txt. The exact resolved dependency set is recorded at /usr/share/doc/cloudimg/wapiti-pip-freeze.txt. The underlying Ubuntu operating system and its system Python are installed from Ubuntu's public apt archive and stay patchable through the normal unattended-upgrades path for the life of the release.
Support
Every cloudimg deployment is backed by 24/7 support. If you have any questions about this image, contact us at support@cloudimg.co.uk.