Post

Using weggli to Hunt Bugs in Damn Vulnerable Web Server

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/
This post is licensed under CC BY 4.0 by the author.