What a RASP, an IAST and a WAF each see of the same attack: a comparison by category

A WAF sees the HTTP request before the application does. An IAST sensor sees data moving through the application code it instruments while tests or traffic exercise it. A RASP agent sees the application's calls from inside the process and can stop one. Each is a different vantage point on the same attack, and each answers a different question well; what they see depends on where they stand and on what they are scoped to follow. This article walks one SQL injection through three delivery routes and describes, by category and with no product named, what each kind of tool observes, the questions worth asking about the one you run, and what a view at the operation itself adds next to them.

If you already run one or more of these, this is meant to help you get more from them, and to reason about what sits between them.

The three categories, in their own terms

WAF. OWASP defines a web application firewall as "an application firewall for HTTP applications" that "applies a set of rules to an HTTP conversation" (OWASP: Web Application Firewall). It sits in front of the application, needs no change to it, and decides on the request as received.

IAST. Interactive application security testing tests the application while an automated test, a human tester or other activity exercises it, and "the core of an IAST tool is sensor modules, software libraries included in the application code" (OWASP DevSecOps Guideline: IAST). It is primarily a testing-phase tool: it reports vulnerabilities with code-level context.

RASP. Runtime application self-protection is recognised as a control in NIST SP 800-53 Rev. 5, enhancement SI-7(17), which describes it as using runtime instrumentation to detect and block the exploitation of software vulnerabilities (NIST SP 800-53 Rev. 5). It runs inside the production process and can stop a call.

All three are established, useful controls. Compliance frameworks name them, and teams that run them catch real attacks with them.

One attack, three routes

Take a single flaw: a report endpoint that builds SQL by concatenation.

import base64
import sqlite3


def find_items(conn: sqlite3.Connection, name: str):
    # the flaw: the value becomes part of the SQL text
    return conn.execute("SELECT id FROM item WHERE name = '" + name + "'").fetchall()


def route_plain(conn, params: dict):
    return find_items(conn, params["q"])


def route_decoded(conn, params: dict):
    # the client sends base64; the SQL metacharacters appear only after this line
    return find_items(conn, base64.b64decode(params["q"]).decode())


def route_worker(conn, message: dict):
    # no HTTP request: a job pulled from a queue by a background worker
    return find_items(conn, message["item_name"])

The fix is the same for all three (conn.execute("... WHERE name = ?", (name,))), and nothing below is a reason to delay it. The question here is what each vantage point observes while the flaw exists.

  • Route A, plain. The injection string is in the query parameter, as typed.
  • Route B, decoded in the application. The parameter is a harmless-looking base64 string. The injection exists only after b64decode.
  • Route C, off HTTP. The value was submitted earlier, stored or queued, and reaches the query in a worker process that never handles a web request.

What a WAF sees

On Route A, the WAF is at its best. A managed SQL injection rule set recognises the shape in the parameter and blocks the request before the application runs a line of code. That is a great deal of value for no application change, and it also absorbs the constant background of automated scanning.

On Route B, the WAF sees the request exactly as sent: a base64 string, which matches no injection signature. Some WAFs decode common encodings before matching, which extends their reach to those encodings; an application-specific transform (a custom token format, a replace step, a field assembled from two parameters) is by nature known only to the application. The WAF's view ends at the application's front door, and the dangerous string is made behind it.

On Route C, there is no request for the WAF to read at the moment of danger. The original submission may have passed through it earlier as ordinary data.

Questions for your WAF: which encodings does it decode before matching? Does it inspect request bodies in the formats your API uses, and up to what size? Which internal, service-to-service paths never cross it?

What an IAST sensor sees

IAST stands inside the application during testing, which gives it something the WAF does not have: the code path. When a test drives Route A or Route B, an IAST sensor that follows the value through b64decode to the query reports the vulnerability with the file and line, and the developer gets a finding they can fix. For Route B that is exactly the visibility the edge lacks.

Its view is shaped by two things. First, it reports what the tests exercise: Route C is found if a test runs the worker with a hostile job, and remains unexamined if not. Second, it follows data through the code its sensors instrument. Products in this category document where propagation ends, and common boundaries are compiled extension modules and parsers written by hand in the application. A value that passes through such a boundary can arrive at the query without its marking.

Questions for your IAST: which frameworks and libraries does it instrument in Python? What does its documentation say about data passing through compiled extensions? Do your test suites run the workers and scheduled jobs, or only the web tier?

What a RASP agent sees

RASP stands inside the production process and can act. On Routes A and B, an agent that hooks the database call sees the final query, after any decoding, and can block it. Seeing the assembled statement is the important property: encoding at the edge no longer matters.

The scope questions are different again. Many agents in this category are designed around the HTTP request lifecycle: they inspect the incoming request, then watch the queries and file operations that result from it. That design keeps overhead and noise down for web traffic. For Route C it means the worker's query has no request to be tied to. Some products in the category focus on a different job, matching loaded libraries against known vulnerabilities, which answers "are we running a component with a published CVE?" and is not aimed at a flaw in first-party code such as find_items.

Questions for your RASP: does it cover processes that serve no HTTP (workers, consumers, cron jobs)? Does it decide on the query alone, or does it know which part of the query came from the request? Which Python database drivers does it hook?

Side by side

WAF IAST RASP
Where it stands in front of the application inside, during testing inside, in production
Decides on the HTTP request as received data flow through instrumented code calls made while serving a request
Route A (plain) sees and can block reports with code location, when tested sees and can block
Route B (decoded in app) sees the encoded form reports, when the flow stays within instrumented code sees the final query
Route C (worker, no HTTP) no request to read reports, when a test drives the worker depends on whether non-HTTP processes are in scope
Strongest at volume, known patterns, no code change developer-ready findings before release stopping an exploit in production
Needs from you rule tuning test coverage a supported framework and an overhead budget

Read the table as a map of vantage points. No row says a category is wrong; each says where it stands.

What a view at the operation adds

There is one more place to stand: the dangerous operation itself, in every Python process, with knowledge of where each part of the value came from. That is the position of es-python, the EyalSec Python runtime. You run the program with es-python in place of python; it marks data as it enters from the sources you switch on (network, files, standard input, environment, and optionally rows read back from a database), follows it through the program as it runs, and checks it at the operation itself: the moment the query is sent, the command is run or the file is opened.

For the three routes, that means:

  • Route A and B are the same event. By the time execute runs, the string is one concrete argument, and the part that came from the network is known, however it was decoded or assembled on the way.
  • Route C is reported in the worker, because detection runs inside the program, not beside the request: any process started with es-python is covered, with or without HTTP. With the db_rows source on, a value read back from the database is followed too, which covers the stored variant.
  • The fixed code is silent. A bound parameter never enters the SQL text, so a correctly parameterized query produces no event.

Each finding is an event: the operation reached, the value, where it entered and the call path. It can be reported only, or stopped under a Raise rule.

The limits belong in the same breath:

  • An event is a real flow, not proof of exploitability. If earlier code compared the value with a fixed list, the flow happened and is harmless. The event gives you what you need to judge.
  • Dataflow only. A missing authorization check, a missing CSRF token or a protocol-level flaw is not untrusted data reaching an operation, so it is outside this view. A WAF and a review process cover ground here that es-python does not.
  • It costs CPU. Following data takes work, and the cost grows with the sources you switch on. Measure it on your own workload.
  • It sees what runs. Like IAST, a path nothing exercises produces nothing.
  • Blocking has precise semantics. At almost every operation the error is raised before the operation runs; at a small number that cannot be interrupted part-way, it is raised immediately after (Report and Raise).

How they fit together

A reasonable arrangement uses each for what it is strongest at:

  1. Keep the WAF for volume, known patterns and virtual patching while a fix is in progress.
  2. Use IAST or runtime tracking in CI so flows are found with code context before release. Running a test suite under es-python is one way to do that; see the pytest tutorial.
  3. Decide what watches the processes that serve no HTTP. Workers and scheduled jobs handle the same untrusted data as the web tier, later and with fewer observers.
  4. Fix the code. Every tool in this article exists because of concatenated strings like the one in find_items. Parameterize, allowlist identifiers, and the routes above stop mattering.

For the model behind events, see How EyalSec works; for the vulnerability class used here, SQL injection; for cost, pricing.

If you would like to see what the operation-level view shows on your own services, next to the tools you already run, you can book a live demo.

EyalSec Pricing Docs Security Research Vulnerabilities Security glossary About Contact עברית Login Book a live demo