Dynamic Analysis Testing: Techniques Beyond DAST
Dynamic analysis means analyzing software by executing it. DAST—sending HTTP attack probes to a running application—is the most common form, but it's just one technique. Dynamic analysis also includes: taint analysis (tracking how data flows at runtime), concolic testing (symbolic execution combined with real execution), memory safety analysis (detecting buffer overflows, use-after-free), and behavioral monitoring (observing system calls and network behavior). Each technique finds different classes of bugs. DAST alone misses most of them.
Key Takeaways
DAST is HTTP-level dynamic analysis. It finds vulnerabilities exposed at the HTTP interface. Vulnerabilities in internal logic, memory safety, or code paths not reachable via HTTP are invisible to DAST.
Taint analysis tracks data flows at runtime. It marks untrusted input as "tainted" and follows it through the application, flagging when tainted data reaches a dangerous operation without sanitization.
Concolic testing combines real execution with symbolic reasoning. It generates new test inputs automatically by reasoning about what inputs would take different code paths—finding bugs without manual test case creation.
Memory analysis catches bugs in native code that SAST misses. Buffer overflows, heap corruption, and use-after-free in C/C++ code are extremely hard to find statically. AddressSanitizer and Valgrind find them at runtime.
Behavioral monitoring detects malicious behavior in dependencies. Watching what system calls and network connections a running application makes can detect supply chain attacks—malicious packages exfiltrating data or creating backdoors.
Forms of Dynamic Analysis
Dynamic analysis is any technique that analyzes software by running it. The major categories:
| Technique | What It Finds | Language Focus | Tools |
|---|---|---|---|
| DAST | HTTP-level vulnerabilities | All (via HTTP) | OWASP ZAP, Burp Suite |
| Taint analysis | Injection vulnerabilities via data flow | Java, Python, PHP | Contrast, Taintgrind |
| Fuzzing | Input-triggered crashes and panics | Any | AFL++, libFuzzer, Jazzer |
| Concolic testing | Logic bugs in complex input parsing | C, C++, Java | KLEE, S2E, Symbolic PathFinder |
| Memory analysis | Buffer overflows, use-after-free | C, C++ | AddressSanitizer, Valgrind |
| Behavioral monitoring | Unexpected syscalls, network connections | Any | Falco, strace, eBPF |
| Coverage-guided execution | Untested code paths | Any | gcov, JaCoCo, LLVM cov |
Taint Analysis at Runtime
Runtime taint analysis instruments the application to tag data at its source and track the tag as data flows through the application.
How It Works
User HTTP Request → Parameter value tagged as "tainted"
│
▼
Application processes parameter
│
┌──────┴──────┐
│ │
Sanitized Not sanitized
│ │
Tag removed Tag preserved
│ │
Safe to use Dangerous sink?
│
Vulnerability
flaggedPython: TaintPy / Taint Tracking
# Example: demonstrating taint flow manually
from flask import Flask, request
import sqlite3
app = Flask(__name__)
@app.route('/user')
def get_user():
user_id = request.args.get('id') # TAINTED: comes from HTTP parameter
# UNSAFE: tainted data directly in SQL query
# Runtime taint analysis flags this
query = f"SELECT * FROM users WHERE id = {user_id}"
conn = sqlite3.connect('app.db')
result = conn.execute(query).fetchall()
return str(result)A runtime taint analysis tool watching this application would observe user_id as tainted (from HTTP input) and flag its direct use in the SQL query as a SQL injection vulnerability—confirmed by actual execution, not just static patterns.
IAST as Taint Analysis
Commercial IAST tools (Contrast Security, Seeker) are essentially runtime taint analysis implementations. They instrument the JVM, .NET CLR, or Node.js runtime to track taint flows across all code paths.
For a deeper look at IAST specifically, see our IAST guide.
Concolic Testing
Concolic (concrete + symbolic) testing executes the program with real inputs while simultaneously maintaining symbolic constraints about what those inputs represent. When a branch is taken, the tool records the constraint that was satisfied and can invert it to generate inputs that take the other branch.
What This Finds
// This function has a vulnerability only reachable with a specific input
void process_packet(char *data, int len) {
if (len > 100) {
// Rarely reached in normal testing
if (data[50] == 0x41) {
// Even more rarely reached
if (data[51] == 0x42) {
// Buffer overflow — only KLEE/concolic testing finds this
memcpy(buffer, data, 200); // buffer is 150 bytes
}
}
}
}Random fuzzing would rarely generate inputs satisfying all three conditions. Concolic testing reasons about the conditions and generates len=101, data[50]=0x41, data[51]=0x42 automatically.
KLEE
# Build with LLVM IR
clang -emit-llvm -c -g -O0 program.c -o program.bc
# Run KLEE
klee --posix-runtime program.bc
# Examine generated test cases
ls klee-out-0/
# test000001.ktest test000002.ktest ...
# Replay a specific test case
klee-replay ./program klee-out-0/test000001.ktestJava: Symbolic PathFinder
// Mark inputs as symbolic for SPF
import gov.nasa.jpf.symbc.Debug;
public class TestTarget {
public static void main(String[] args) {
int x = Debug.makeSymbolicInteger("x");
int y = Debug.makeSymbolicInteger("y");
testFunction(x, y);
}
}Memory Safety Analysis
Critical for C and C++ codebases. Memory errors—buffer overflows, use-after-free, double-free—are the root cause of most critical CVEs in systems software.
AddressSanitizer (ASan)
Built into GCC and Clang. Near-zero configuration. Finds buffer overflows, heap corruption, stack corruption, use-after-free.
# Compile with ASan
gcc -fsanitize=address -g -o myapp myapp.c
# Or with clang
clang -fsanitize=address -g -o myapp myapp.c
# Run—ASan instruments the binary automatically
./myapp
# Example output when a bug is found:
# ==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000010
# READ of size 4 at 0x603000000010 thread T0
# #0 0x401234 in process_data myapp.c:47
# #1 0x402345 in handle_request myapp.c:123Valgrind / Memcheck
Slower than ASan (~10–50x overhead) but doesn't require recompilation:
valgrind --tool=memcheck \
--leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
./myappThreadSanitizer (TSan)
Detects data races in multithreaded programs:
# Compile with TSan
clang -fsanitize=thread -g -o myapp myapp.c
./myapp
# ==12345==WARNING: ThreadSanitizer: data race
# Write of size 4 at 0x7f9d8c0027f0 by thread T2:
# #0 increment counter.c:15
# Previous read of size 4 at 0x7f9d8c0027f0 by thread T1:
# #0 read_counter counter.c:10UndefinedBehaviorSanitizer (UBSan)
Catches undefined behavior: integer overflow, null pointer dereference, misaligned accesses:
clang -fsanitize=undefined -g -o myapp myapp.cBehavioral Monitoring
Behavioral analysis watches what a running application does—not what its code says. This is particularly useful for detecting:
- Supply chain attacks (malicious npm packages making network calls)
- Unexpected file system access
- Privilege escalation attempts
Falco (Runtime Security for Containers)
# falco-rules.yaml — custom rule to detect unexpected outbound connections
- rule: Unexpected outbound connection from application
desc: Application made an outbound connection to an unexpected destination
condition: >
outbound and
container.image.repository = "myapp" and
not fd.sip in (allowed_ips)
output: >
Unexpected connection from myapp (user=%user.name
command=%proc.cmdline connection=%fd.name)
priority: WARNINGstrace for Process Analysis
# Trace all system calls from a suspicious binary
strace -f -e trace=network,file ./suspicious_binary 2>&1 | \
grep -E "connect|open|write" | head -50
# Watch for file system writes to sensitive paths
strace -e trace=openat,write ./myapp 2>&1 | grep -E "(/etc/passwd|/etc/shadow|\.ssh)"eBPF for Production Monitoring
// Minimal eBPF program to trace execve calls
SEC("tracepoint/syscalls/sys_enter_execve")
int trace_execve(struct trace_event_raw_sys_enter *ctx) {
char filename[256];
bpf_probe_read_user_str(filename, sizeof(filename), (void *)ctx->args[0]);
bpf_printk("execve: %s", filename);
return 0;
}Tools like Tetragon, Cilium, and Falco use eBPF to provide low-overhead behavioral monitoring in production.
Integrating Dynamic Analysis in Your Pipeline
Development
├── Memory sanitizers (ASan/TSan/UBSan) in debug builds
└── Run test suite with sanitizers enabled
CI/CD
├── Fuzzing (short runs — 60–300 seconds per fuzz target)
├── DAST against ephemeral staging environment
└── IAST with regression test suite
Staging
├── Extended fuzzing (hours per target)
├── Concolic testing for critical parsers
└── Behavioral monitoring to detect unexpected activity
Production
└── Behavioral monitoring (eBPF/Falco) for anomaly detectionChoosing the Right Dynamic Analysis Technique
| If you're trying to find... | Use... |
|---|---|
| Injection vulnerabilities in web apps | DAST + IAST |
| Memory safety bugs in C/C++ | ASan + Valgrind + fuzzing |
| Logic bugs in complex parsers | Concolic testing (KLEE) |
| Race conditions | ThreadSanitizer |
| Supply chain / backdoor detection | Behavioral monitoring (Falco, strace) |
| Untested code paths | Coverage instrumentation (gcov, LLVM cov) |
| Input-triggered crashes | Fuzzing (AFL++, libFuzzer) |
Summary
DAST gives you HTTP-level coverage of your running application. But dynamic analysis is a much broader discipline:
- Taint analysis follows data flows from user input to dangerous sinks—finding injection vulnerabilities with fewer false positives than SAST
- Concolic testing generates test cases that reach code paths random testing misses
- Memory sanitizers catch the memory safety bugs that cause most critical CVEs in native code
- Behavioral monitoring detects supply chain attacks and anomalous runtime behavior
Combining multiple dynamic analysis techniques gives you coverage that no single tool can provide.