Introducing jswzl: In-depth JavaScript analysis for web security testers

Charlie Eriksen

July 14, 2023

Introducing jswzl: In-depth JavaScript analysis for web security testers

For the last year, I’ve been working hard on creating what I believe to be the next stage in the evolution of tooling for penetration testers, web application security testers, security analysts, and security engineers—in short, anyone who spends time testing web applications. And today, I’m happy to announce that it’s finally available for everybody to use!

In a previous blog post, I discussed some of the inspiration and existing tools doing JavaScript analysis for security testing. To celebrate the milestone we’ve reached today, I wanted to take some time to discuss some of the significant challenges I’ve solved with jswzl and how they help you be more effective in your quest to find security vulnerabilities!

The post will be split into multiple parts, covering two major domains in which jswzl runs.

1 Integration with existing tooling

1.1 Burp Suite

While jswzl is not tied directly to Burp Suite, the initial usage will depend on Burp Suite. Installing the jswzl extension will ensure that any JavaScript that’s proxied through Burp gets sent to the analysis server.

This integration makes jswzl a seamless addition to your existing workflow. It operates quietly in the background, working its magic. Plus, it enables two-way communication to perform tasks like fetching source maps and pre-fetching packed chunks, which I'll describe in more detail below.

1.2 Visual Studio Code

Addressing the challenge of displaying source code with rich context led me to the realization that existing tools already solve the problem efficiently. Why reinvent the wheel?

With a straightforward extension for VS Code, you can view all files that jswzl has analyzed and see a request history. All your favorite existing extensions work flawlessly, providing a very natural experience. The days of trying to browse code in a tiny panel in Chrome Dev Tools are behind us.

2 Analysis engine

The analysis engine is where the real magic happens. A lot goes on here that might not be immediately apparent. Let's examine some of its key components.

2.1 Source maps and chunks

To provide the most accurate and comprehensive analysis possible, it's crucial to process all relevant code and initially, maintain its original form as closely as possible. This is accomplished in two stages:

First, jswzl fetches source maps. Whenever a .js file is requested, jswzl sends another request to see if a file also exists. If it does, that source map is applied to the source. In many cases, you can even retrieve the original source in its entirety!

It's often possible to recover close to the original source thanks to source maps.

Secondly, jswzl looks for chunks that are lazy-loaded. This part is surprisingly complex, given how Webpack functions. Heuristics will detect lazy-chunk-loading code and evaluate it in a small custom runtime/sandbox to compile a list of all chunk names. These chunks are then loaded.

Determining chunk file name can be complex, requiring an interpreter.

Loading chunks eagerly is particularly crucial in larger applications, where most of the source is loaded as needed. This way, you won't need to manually spider the application to make it load all its chunks.

2.2 Extract packed files

Sometimes, the source packing retains the original file structure in the code. This means we can often extract the entire source tree with high fidelity, making it much easier to obtain a comprehensive overview of the application's structure.

It's often possible to recover the full source tree from packed code!

2.3 Variable dereferencing

It’s easy to lose out on meaningful data in source code if it heavily uses variables needlessly (see example in the next section)—jswzl ships with a simple interpreter, which can dereference variables for analysis. The next step (Optimize) clearly demonstrates why this is important.

2.4 Optimize

Some JavaScript code can seem strange, whether due to minification, packing, or peculiar coding patterns. Consider this piece of code, which resembles something I've encountered during jswzl's development:

It's pretty obvious what the result will be of this code.

This code is equivalent to the code seen below. Since there's no semantic difference between the two, jswzl will rewrite it, giving this more straightforward form.

Ahh, much better.

2.5 Prettify

Minified code can be impossible to read. Therefore, jswzl always consistently prettifies the code to enhance readability.

Ugly code? Ain't nobody got time for that.

2.6 Descriptor extraction

Having skimmed over a lot of detail, we now reach the stage that results in the output you see in the tool: extracting descriptors.

To understand a descriptor's nature, you must know that it represents a part of the AST and can take the following forms:

- String Expression

- Object Schema

- Call Expression

Each of these forms is more complex than the last. For instance, a string literal can contain a path (String Expression), an object can contain a path and headers to set for an HTTP request (Object Schema), and a method call to make an HTTP request includes arguments through an object (Call Expression).

By performing multiple passes over the AST, we first tag nodes with String Expressions, then Object Schemas, and finally, Call Expressions.

3 Putting it all together

While this overview isn't exhaustive, it provides a glimpse into the components that make up jswzl. But how does this impact you, and how can it enhance your testing workflows?

Imagine you're testing a large and complex Single-Page Application (SPA). One of the first things jswzl will do is provide you with a list of all path-looking strings. You can simply copy these values and drop them into Burp Intruder to get an overview of existing endpoints.

You've now identified paths that might interest you. But how do you interact with these endpoints in a meaningful way? By identifying the call sink, jswzl can often show you the data structure that the endpoint expects, thereby making manual testing far more straightforward and faster in most cases.

4 What’s next?

Today is just the beginning of the journey. Thanks to all those who helped beta test the tool, it's already significantly aiding testers in their daily quest to find cool bugs.

However, I have many plans to evolve the tool further and provide previously unachievable capabilities. Initially, the focus will be on ensuring the tool is easy to pick up and use. After that, I'm excited to explore the many fascinating opportunities for scaling this across teams and organizations based on the concept of taking source code and reducing it to a set of descriptors.

This concept opens up a whole new world of possibilities that I've likely not even considered yet. Feedback from the community and users will be pivotal in determining the roadmap. I'm always eager to hear from people and understand how to empower testers to do their best work.