Posts Tagged ‘API’

A closer look at Dine & Discover NSW Vouchers

25 Mar 2021


The NSW Government has launched Dine & Discover NSW to encourage the community to get out and about and support dining, arts and tourism businesses.

NSW residents aged 18 and over can apply for 4 x $25 vouchers, worth $100 in total.

  • 2 x $25 Dine NSW Vouchers to be used for dining in at restaurants, cafés, bars, wineries, pubs or clubs.
  • 2 x $25 Discover NSW Vouchers to be used for entertainment and recreation, including cultural institutions, live music, and arts venues.

The Vouchers

There are two ways to access these vouchers:

  1. Email – Vouchers are initially emailed to eligible participants of the program after they sign up.
  2. ServiceNSW App – Vouchers can be accessed from the ServiceNSW

The initial email contains a PDF attachment that contains vouchers that look like this:

Discover NSW Voucher

Dine NSW Voucher

Each voucher has a few pieces of metadata attached:

  • The voucher type (Dine or Discovery)
  • A 9 character voucher code such as ED04K31MB (omitted from screenshots above)
  • A QR code with embedded data
  • An expiry date (in this case 30-Jun-2021)
  • Value (in this case $25)

Using the vouchers

There are two ways you can redeem your vouchers online or in-person:

  1. You can supply a participating business with your 9-char voucher code such as ED04K31MB
  2. You can show a participating business the QR code which exists to promote COVID-safe contactless interactions.

Email Vouchers vs ServiceNSW App Vouchers Discrepancy

There appears to be a discrepancy between the voucher codes and QR codes sent in the original email and the ones that appear in the ServiceNSW mobile app. The codes sent in the email seem to be the original codes and are “long life”. The codes generated in the ServiceNSW app seem to be generated each time you click on a voucher. They are likely temporary codes that, in the end, are all linked back to the unique identifier for your voucher. So once you use the code in the email or any code generated by the app, your uniquely identified voucher will be marked as used.

ServiceNSW App Dine Voucher

I didn’t test using old codes though but I’ll give ServiceNSW the benefit of the doubt here. 😉

Voucher format

Dine and Discover NSW Voucher Format

Each voucher is 9 characters in length.
Discovery vouchers always start with the letter G. Dine vouchers always start with the letter E.
The remaining 8 characters seem to be random alphanumeric characters (case-insensitive).

That QR code though…

Scanning any of the voucher QR codes gives us some plain text.

Dine/Discover Voucher (ServiceNSW App)

For example here is the output from scanning a Discover voucher from the ServiceNSW app:

This is a Base64 encoded string that represents the following JSON object:

We have a few fields present:

  • v: voucher
    • d: data – the first 8 characters here appended to G or (based on type) to create the voucher code; it is unclear what the last 8 digits represent (checksum or additional code?)
    • t: type of voucher, either CSG (for discovery vouchers) or CSE (for dine vouchers)
  • ts: timestamp – ISO8601 time string representing the time the voucher code was generated
  • m: unknown – as a 64 character hexadecimal hash it may be a SHA256 hash but I am not sure what it could represent (perhaps member?)

Dine/Discover Voucher (Email)

Interestingly, the QR code output from the vouchers in the email is different.

For example here is the output from scanning a Dine voucher from the email:

This is a Base64 encoded string that represents the following JSON object:

We have a few fields present:

  • v: voucher
    • d: data – this appears to be the unique identifier of our voucher in UUID format (which is what the temporary voucher/QR codes from the ServiceNSW app are likely linked to)
    • t: type of voucher, either CSG (for discovery vouchers) or CSE (for dine vouchers)

Notice how the ts (timestamp) and m (unknown) fields are missing here.

Third-Party Validation API

The obvious attack vector here is the participating businesses that accept these codes. There is likely a private API exposed by ServiceNSW and provided to participating businesses to validate and use these vouchers. I discovered that Hoyts (a participating business) was using this API behind the scenes through their own API proxy here:

This API takes a voucher code and returns a few responses based on the result of the validation.

Valid (unused) voucher:

Valid (unused) voucher but the user has already redeemed voucher at this business during this business day:

Valid (unused) voucher but wrong type:

Invalid (already used) voucher:

Invalid (not found) voucher:

Invalid (wrong format) voucher:

Bruteforce attack

Each voucher code is 9 characters long. As the first character is hardcoded, this leaves only 8 characters. It appears that the next 8 characters are random alphanumeric characters. Furthermore, by testing an API we discover we can confirm that upper and lowercase characters are equivalent. In other words, the voucher codes are case insensitive.

This leaves us with a character set of 36 characters (26 letters of English alphabet + 10 digits):

There are roughly 8 million residents in NSW. Modestly assuming 60% of those are adults over 18 and eligible for the NSW Dine & Discover voucher program, there are potentially up to 4.8 million vouchers originally generated and sent to a users email address. However, the total number of unique 8 character combinations with the above character set is 36 pick 8, or 2 821 109 907 456 (~2 trillion). Double that for each voucher type. That means that at best only 0.0001701% of the maximum combinations will be valid voucher codes for each voucher type. We are ignoring the temporary generated voucher codes here as they may expire and are likely not frequently generated until just before they are used.

If this were a password hash, with only 8 characters of entropy and a 36 character set, it would definitely be feasible to brute force the entire combination set. 8 character passwords with a character set this small are not secure and can be cracked offline from a hash within 10 – 30 days with moderately cheap hardware.

Unfortunately, though, we are forced to validate our codes online through the Hoyts API. This is much, much slower than GPU hash cracking due to the cost of networking and has other unwanted side effects:

  • Slow network-based validation technique
  • Raising suspicion by server maintainers through metrics they collect around API usage
  • Bringing additional load onto Hoyts servers and by extension ServiceNSW servers
  • Running into rate limiting issues (severely reducing the effectiveness of our brute force attack)

Bruteforce attempt and results

Nevertheless, in the spirit of giving it a go and in good faith I attempted a hopeless brute force attempt. I started a cluster of AWS lambdas in the ap-southeast-2 region running a simple Python scraping script. For simplicity, I logged any unexpected API responses to Cloudwatch. This code will not be released publically for obvious reasons. Furthermore, I time-boxed this experiment to only 12 hours as I would prefer to not needlessly burn money through AWS fees.

Unsurprisingly, I found no other valid voucher codes and that is good news for everyone.

Now get out there and kick start NSW again!



Posted in Penetration Testing


Disable Slack @channel and @here notification for all channels

01 Sep 2020


Slack can get very noisy if you are part of a big organisation. Slack offers various notification controls on a per-channel basis.

You can choose to be notified if:

  • There is a new message
  • Somebody mentions you
    • Somebody mentions @channel or @here
  • Never

For example:

Slack Notification Options for a Channel

Slack points out that you can tweak your workspace-wide settings in your preferences. However, these settings do not mimic the per-channel options. They are missing the ability to suppress @channel or @here mentions. This is the main annoyance with Slack notifications! Getting a ping for a channel you are in that has nothing to do with you. My org actually discourages using these mentions in some channels but somebody eventually does (an easy mistake to make) which immediately summons a hoard of angry emoji reactors who have lost their state of focus.

Slack Workspace Wide Notification Preferences

Slack pls help?

Contacting Slack support did not help in this case. This feature was not planned on their roadmap for the foreseeable future. The only options were to manually update every single channel or turn off all notifications.

Script Solution!

Luckily, we can write a script to solve this problem for us!
It works by fetching a list of all Slack channel ids from the api/client.boot endpoint and then calling the api/users.prefs.setNotifications endpoint to update the preferences for each channel. A delay is used between each update to prevent server-side rate limiting.

First, download the latest version of the script from here:

Or copy and paste it from here:


  1. Visit the React web app which powers the React native Slack client at:
  2. Sign in and switch to the workspace of interest and wait for the page to fully load.
  3. Open your browsers developer console
  4. Execute the following line:

    It should output an object with a bunch of team ids. Copy and paste the id of the workspace of interest.
    In my example its: EXAMPLE17
  5. Replace the slackTeamId in the Javascript script with your id from Step 4.
  6. Copy and paste the edited Javascript script into the developer console and execute it.
    The script may take a few minutes to run depending on the number of channels you have joined.slack_user_noti_pref_bulk_update.js script execution

Posted in Programming


How to delete a Ghost/Empty SignalFx Dashboard Group

19 Jun 2020

The Problem

I ran across into issue when working with SignalFx where I was unable to delete a dashboard group that contained no dashboards. The only way to delete a dashboard group is to first visit a dashboard belonging to the dashboard group itself then using the meatball menu next to the dashboard group name to delete the group and all dashboards/charts belonging to the group.

This is an example of, what I have coined, a ghost dashboard group:
SignalFX Ghost Dashboard Group

The only workaround is to use the SignalFx API to retrieve the dashboard groups id and then use that to visit a special link.


  1. Use your favourite tool to make an API request to the following SignalFx dashboard group endpoint and set the name parameter to the name of the dashboard group you are trying to delete.
  2. Retrieve the dashboard group id from the response.
  3. Find a working valid dashboard in your organisation and copy the link. It should look something like this:
  4. Replace the groupId parameter in the link above from the id we retrieved from step 2.
  5. Visit this URL. The page should load with the same dashboards but with the group belonging to the dashboard group we want to delete.
  6. Delete the ghost dashboard group using the option in the meatball menu.
    Note: The dashboard being shown on the page will not be deleted, only the ghost dashboard group will be.

    SignalFX Delete Ghost Dashboard Group


Posted in Miscellaneous


Hackvent 2019: Day 11

11 Dec 2019
CTF: Hackvent 2019
Link to challenge:
Date Completed: 11 December 2019


HV19.11 Frolicsome Santa Jokes API

Html file mirror: FSJA API Description


We have the spec for the FSJA API that the elves have made. We use Postman to play around with the API to get a feel for how it works.

Following the instructions, we are able to register a new user and authenticate to get a token.
We use the following payload for our user data:

Upon logging in with the /fsja/login  endpoint we get a token which looks like this:

The token looks like base64 encoded data. In fact, it happens to be a JWT token.

We finally use the /fsja/random  endpoint to get a joke:

The platinum field stands out to me the most.
As a random hunch, I decide to register a user and provide the  platinum field value in the payload myself like so:

I generate another joke and the API kindly provides us with our flag:

Flag:  HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}


This challenge also contained the solution to HV19.H2 Hidden Three

No Comments

Posted in Hackvent 2019


Fixing Mixed Content warnings using CRONjobs

02 Dec 2015

So if you, like myself, have a HTTPs only website you may have noticed that your green bar, green label or security shield (image below) disappears if your webpage fetches an image from another website over HTTP and not HTTPS.

Chrome Secure Green Lock

HTTPS Secure Chrome Lock

Now this isn’t an issue in itself which is why all modern browsers only produce a small warning in the console.

Console Warning Message

Mixed Content Message in Console

Unfortunately, most browsers also remove the secure label which is unfortunate as most business websites want to display their secure logo for customer reassurance reasons if nothing else. Personally, I just think it looks cool so I like to keep it green.

Easy (Obvious) Solution

The obvious solution is obviously  to host the image yourself or move the image to a website that supports HTTPS (like

However! The real issue is when the image is being produced by some API and you do not have access to the source code of the script producing the image.


Okay so in this case, we are using some API which regular updates an image.
We want our CRON job to run daily (or whenever required based on your needs) and to download that image and store it locally, so that your website has access to it (over HTTPs!)
This makes all the mixed content errors disappear.
I came up with the following bash script in my case:

Simply save this as and add a CRONjob to run the script at some interval (like daily at 3AM when your server isn’t being used much).

Check out this post on how to make CRONjobs:

No Comments

Posted in Server Management