Using weggli to Hunt Bugs in Damn Vulnerable Web Server
I wrote Damn Vulnerable Web Server (DVWS) as a deliberately insecure C/C++ web server to practice vulnerability research against something that feels closer to a real native application instead of another intentionally vulnerable PHP app.
Repo:
https://github.com/snoopysecurity/damn-vulnerable-web-server
The project includes bugs like:
- stack overflows
- heap overflows
- use-after-free
- type confusion
- command injection
- format string bugs
- path traversal
- race conditions
While building and testing it, one of the tools I kept using was weggli.
If you’ve never used it before:
https://github.com/weggli-rs/weggli
it’s basically semantic grep for C/C++.
Instead of searching for exact strings, you search for code patterns.
That makes it ridiculously useful for vulnerability research.
This post is more of a practical walkthrough showing how I’d actually use weggli against DVWS.
Setting up
Install weggli first:
1
cargo install weggli
or:
1
brew install weggli
Clone DVWS:
1
2
git clone https://github.com/snoopysecurity/damn-vulnerable-web-server
cd damn-vulnerable-web-server
Now we can start hunting.
Start broad
The first thing I usually do against any old C/C++ codebase is look for dangerous APIs.
For example:
1
weggli '{ strcpy($a, $b); }' .
This immediately surfaces places where user-controlled data may end up copied into fixed-size buffers.
In DVWS this quickly leads to things like:
1
2
char clean_path[200];
strcpy(clean_path, path_with_query);
which is obviously dangerous.
The nice thing about weggli is that it doesn’t care about variable names.
So whether the code says:
1
strcpy(buf, input);
or:
1
strcpy(destination_buffer, user_data);
it still matches.
Looking for stack overflows
Once I find unsafe copies, I usually refine the query.
Instead of searching for every strcpy, I want specifically:
- fixed-size stack buffers
- unsafe copies into them
So I’ll run:
1
2
3
4
5
weggli '
{
char $buf[$size];
strcpy($buf, $src);
}' .
This cuts down noise a lot.
You can do the same for formatting bugs:
1
2
3
4
5
weggli '
{
char $buf[$size];
sprintf($buf, _, _);
}' .
or:
1
2
3
4
5
weggli '
{
char $buf[$size];
snprintf($buf, $len, _);
}' .
A lot of older network services still contain bugs like this.
Hunting path traversal
DVWS also intentionally builds filesystem paths from user-controlled input.
This is the relevant code:
1
2
strcpy(file_path, SERVER_DIR);
strcat(file_path, clean_path);
That’s basically the standard recipe for path traversal.
A nice query for this kind of thing:
1
2
3
4
5
6
weggli '
{
strcpy($path, $base);
strcat($path, $user);
fopen($path, $_);
}' .
This works really well against:
- embedded web panels
- NAS software
- firmware
- backup systems
I also like using negative matching:
1
2
3
4
5
weggli '
{
strcat($path, $user);
not: realpath($path, _);
}' .
This looks for path handling without canonicalization.
Negative queries are honestly one of the best parts of weggli.
Finding command injection
DVWS contains intentionally vulnerable shell execution code:
1
2
command = "sh -c \"grep " + decoded_filter + "...";
popen(command.c_str(), "r");
A really effective query for this class of issue:
1
2
3
4
5
weggli '
{
std::string $cmd = $a + $user + $b;
popen($cmd.c_str(), $_);
}' .
You can broaden it further:
1
2
3
4
weggli '
{
system($cmd);
}' .
or:
1
2
3
4
weggli '
{
execl("/bin/sh", _, _, _);
}' .
This is one of the first things I search for while auditing routers and IoT firmware because people still build shell commands with string concatenation constantly.
Hunting format string bugs
DVWS also contains:
1
fprintf(log_file, value.c_str());
which becomes dangerous if the attacker controls the string.
Easy query:
1
2
3
4
weggli '
{
fprintf($fp, $user.c_str());
}' .
Also useful:
1
2
3
4
weggli '
{
printf($user);
}' .
and:
1
2
3
4
weggli '
{
syslog($prio, $user);
}' .
Format string bugs are less common these days, but when you find one they can still be extremely powerful.
Looking for heap overflows
DVWS has an intentionally vulnerable upload handler:
1
2
3
4
unsigned int buffer_size = content_len + 64;
malloc(buffer_size);
recv(sock, buffer, content_len, 0);
This is a classic integer overflow → heap overflow pattern.
Good weggli query:
1
2
3
4
5
6
weggli '
{
unsigned int $size = $len + $C;
$buf = malloc($size);
recv($fd, $buf, $len, _);
}' .
These patterns show up constantly in parsers and network services.
You can also search more generically:
1
2
3
4
5
weggli '
{
malloc($size);
recv(_, _, $len, _);
}' .
then manually review matches.
Hunting use-after-free bugs
One thing I like doing with weggli is searching for memory lifecycle mistakes.
For example:
1
2
3
4
5
weggli '
{
free($ptr);
not: $ptr = NULL;
}' .
This finds freed pointers that were never invalidated.
Then I’ll refine further:
1
2
3
4
5
6
weggli '
{
free($ptr);
_
$ptr->$field;
}' .
This helps surface potential use-after-free candidates.
Not every result is exploitable obviously, but it narrows things down fast.
Looking for unsafe casts
DVWS also has intentional type confusion issues using unsafe casts.
Something like:
1
ExecRule* exec_rule = static_cast<ExecRule*>(rule);
A good starting query:
```bash id=”9eyb2g” weggli ‘ { $Derived* $x = static_cast<$Derived*>($base); }’ .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Then manually review whether type validation actually happens.
This works surprisingly well against:
* browser code
* IPC frameworks
* large C++ applications
---
### Hunting insecure temp files
DVWS creates predictable files in `/tmp`:
```c
/tmp/php_script_%d.php
which is classic race-condition material.
Simple query:
1
2
3
4
5
weggli '
{
snprintf($path, _, "/tmp/%_", _);
fopen($path, "w");
}' .
When I find these I usually check for:
- symlink attacks
- TOCTOU races
- file overwrite opportunities
One thing that makes weggli really good. The feedback loop is fast.
You usually start with something broad:
1
weggli '{ strcpy($a, $b); }' .
Then refine it over time:
1
2
3
4
5
6
weggli '
{
char $buf[$size];
strcpy($buf, $src);
not: strlen($src) < $size;
}' .
Eventually you end up with patterns tailored specifically for the codebase you’re auditing.
That’s where weggli becomes really powerful.
Final thoughts
DVWS was built mainly as a playground for native vulnerability research.
The bugs are intentionally vulnerable, but the patterns themselves are very real and still show up in production software all the time.
If you’re learning:
- C/C++ auditing
- exploit development
- firmware research
- native bug hunting
I’d definitely recommend spending time with weggli.
Once you get used to semantic searching, going back to normal grep feels painful.
Useful links:
- https://github.com/snoopysecurity/damn-vulnerable-web-server
- https://github.com/weggli-rs/weggli
- https://hnsecurity.it/blog/a-collection-of-weggli-patterns-for-c-cpp-vulnerability-research/