How I added daphne server dectection to wappalyzer

 · 7 min read
 · Baseplate-Admin
Last updated: June 19, 2023
Table of contents

Back in the end of 2021 I was really amazed by wappalyzer. This seemingly small piece of extension can detect what the backend is ( albeit not guranteed ).

As I was relatively new to django back then I was amazed that many of the top sites are powered by django ( ・┆✦ʚ♡ɞ✦ ┆・ )!!! This was honestly a shock to me as most of people around me are talking all about PHP and stuff ( I mean PHP is not a bad language | Just gives too many opportunity to shoot yourself in the foot with bad practices )

Context

django is a great framework written in python. It is robust, secure and generally pleasent to work with. The ASGI ( Asynchronus Server Gateway Interface ) framework written by django core team ( speally andrew ) was daphne. dapnhe was not detected by wappalyzer. This was annoying (ミ ̄ー ̄ミ)

Modifying Wappalyzer

So being annoyed at daphne not being detected by wappalyzer ( which I mean it should ). I opened a pull request in the wappalyzer repo. Which modified these lines

 "Daphne": {
    "cats": [
      22
    ],
    "headers": {
      "Server": "Daphne"
    },
    "implies":[
      "TwistedWeb",
      "Python",
      "Zope"
    ],
    "oss": true,
    "icon": "daphne.svg",
    "website": "https://github.com/django/daphne"
  },

The Pull Request was accepted by @aliasio but I was shocked to see that daphne was still not detected by wappalyzed. Then diving deeper, It was being detected on my social media project but not on my other project. Panic seized me as I was frantically looking for answers. It couldn't be happening cause I saw that server header detection was how wappalyzer understood the backend software. I was pretty much sure that I saw daphne on the server header on my chat application.

Daphne Server Header on websocket

But then i realized that the header was only being sent on websocket responses but not on http responses.

Opening an issue on github

I was quick to go to django daphne github to open an issue to investigate deeper into this mess. I thought that django was hiding daphne header intentionally to block out attackers ( this is not a good way to handle such use cases tbh ).

@adamchainz was quick to respond to me. Confirming that I was right on thinking that django daphne intentionally ( or maybe we had some sort of misunderstanding ).

Regardless I wanted the web to be open ( and by a large means allow others to see what they are doing ). So i tried defending my point. By pointing out that other web servers do they same and why should django daphne do things differently ? (django + daphne wasn't event that common | django + uvicorn was back then)

Then i pointed out other web servers doing things differently:

  • Gunicorn :

    • Captured from : https://launchpad.net/ Gunicorn Shows server header
  • Uvicorn :

So why was daphne ( a less popular server doing things differently )? Also they were already exposing server headers via websockets !!!

I countered @adamchainz argument

Adam : since it allows targeting servers when vulnerabilities become known

Me : I totally agree with this. So we can add an option ( or use a django middleware to modify the headers ) to disable header for mission critical servers. eg:
if SERVER_HEADER_HIDDEN:
    headers.pop('Server')

Adam could finally understand me and he proposed a Pull Request to resolve this issue.

I was thrilled to get a chance into working into django.. I mean it's django. The most popular web framework for python!!

Opening a Pull Request to resolve this issue

When I looked into daphne source code. I came to realization that they are using twisted for their network stack. They were using autobahn for their websocket stack. Some poor chap before me modified the autobhan header to be daphne header and thats where I was getting the awkward server header thingy.

I came across a piece of code that was doing nothing but it felt like was doing something. ( the server header will not be daphne duh )

if self.server.server_name and self.server.server_name.lower() != "daphne":
   self.setHeader(b"server", self.server.server_name.encode("utf-8"))

I replaced it with my own custom code

if self.server.server_name and not self.responseHeaders.hasHeader("server"):
    self.setHeader(b"server", self.server.server_name.encode())

and low and behold things were looking very promising to begin with.

Then adam reviewed my code once again.. This change was not done by me. It was done by black!!!

After some discussion I was able to resolve this issue and then ran into another. I have never written tests for software ( now that i am a bit older i know how important tests are ) So once again I was down a wild goose chase figuring out how to fix the tests.

After some digging around i wrote the tests

 def test_no_servername(self):
    """
    Passing `--no-server-name` will set server name to '' (empty string)
    """
    self.assertCLI(["--no-server-name"], {"server_name": ""})

And this one too ( though adam helped me here )

return sorted(
        [(b"server", b"daphne")]
        + [
            (name.lower(), value.strip())
            for name, value in headers
            if name.lower() not in (b"server", b"transfer-encoding")
        ]
    )

I then rewrote the CHANGELOG but ran into another roadblock. My wording was not good ?

My wording : Added default headers to server response, so that if a response from backend didn't contain any server header it will automatically add a daphne server header.
Adam's wording : Always add the `Server` header, with value `Daphne`, when it isn’t already present.

This didn't sound good to me. I counteracted with my own points.

The header is hard coded for Websocket . We cannot change it from backend. ( Even if backend sends a header for websocket daphne overrides that | Thus it wasn't a correct implementation ). Check code and Issue and the pull request to change it. Because when i profiled daphne, server header was a mask over Autobahn. Otherwise daphne reports that it's running AutoBahn and Twisted.

Then Carlton dropped in and shared his view. As I feared he thought this can be used for malicious intent.

I again hopped in and defended my view. I atleast want to give developers who share my view of a open web a chance to show their stack. ( this wasn't even possible as daphne would filter out the server header )

Some chap before me tried to fix this issue. But he couldn't get it right and for some reason andrew thought things were fine.

Then adam hoped in and saw my perspective.

I understood the plan and gave developers the option to set custom server name or ( none if they prefer ) set a default one if there was no argument give.

But still to my avail the Pull Request was not merged. At this point I thought there was nothing more I can do but to give them time.

After 2 months I again pinged carlton for a review. If this will ever get merged or will this get closed or not.

He disappeared again. At this point i moved away from this issue and continued my life ( as i had an important exam in December and i couldn't afford to waste time like this ) but to my surprise near april the PR was merged. This was unexpected to be honest and i was overwhelmed with joy.

Thats how I got my first PR merged into django daphne

If you have see any problems in this article please raise an issue the github repository