❌

Reading view

I made a pixel-art Halloween quest to resurrect RoadRunner. Live run Oct 31 at 16:00 UTC 🎃

When RoadRunner 3 came out, some people took it as the beginning of the end. Right here on r/PHP:

So EOL for RR soon?

Rumors travel fast, and by now some folks are pretty sure RoadRunner is dead.

Well. Tomorrow is Halloween, and that's a chance to resurrect RoadRunner. 🎃

So I made an interactive pixel-art quest for it. It's a multiplayer game: you make a little pixel character, and everyone's characters walk around the same shared scene, so you see what the others do in real time.

The bar is already open: https://halloween.roadrunner.dev

It starts in a viking bar. The owl behind the counter swears RoadRunner can be brought back: something about pumpkins, a ritual, and a magical place nearby. Grab a mug, find someone to clink with (no clink, no drink: house rules), shoot some flies and listen to the owl's gossip.

🪰 Rumor has it that everyone who swats 100 flies before the ritual starts will get a superpower.

🕓 Saturday, Oct 31, 16:00 UTC: the ritual

Your local time

That's when the doors to the pumpkin patch open and we go through the whole story together, live, with the owl leading the way. What happens after that… you'll have to be there. No spoilers.

Join the crowd, clink mugs with strangers and help with the ritual 🍺

Missed it? Play it again. And again.

After the live event I'll step down as the game master and switch the game to autopilot for a week. No moderator needed: hit the goal of a scene and the story moves on by itself. Bring your friends and play the whole thing from the bar to the very end, then start over, as many times as you like.

You don't need to play. Watching is fine, and it works on your phone. More people means a bigger ritual and more beer.

How it works

The backend is PHP. The stack:

  • PHP with the Spiral framework
  • RoadRunner: HTTP, KV, locks, jobs, metrics (no nginx)
  • Centrifugo for WebSockets
  • Valkey and in-memory KV storage
  • Prometheus and Grafana
  • PixiJS on the frontend

All of it runs on one VDS: 2 × 3.3 GHz CPU, 2 GB RAM, 15 GB NVMe. By my estimate that's enough for about 200 active players. Let's find out tomorrow 😈

How it all fits together, with diagrams and code: https://halloween.roadrunner.dev/how-it-works

Live metrics in Grafana: https://halloween.roadrunner.dev:3000/d/hellowin/hellowin

Yes, the irony of running "RoadRunner is dead" on RoadRunner is intentional.

See you at the bar. 🍺🎃

submitted by /u/roxblnfk to r/PHP
[link] [comments]
  •  

Site compromised, interesting implementation of a persistent virus

Hello PHPers :)

I was tasked to look into two compromised WordPress sites. The people who own them, tried to remove the virus but failed. I wanted to describe what the virus was doing.

Overall this is a self-propagating, self-healing, obfuscated, hidden executor virus with size-padding evasion tactics.

It generated the following files and directories:

  • /home/foobar/public_html/wp-content/.sc_494g1672
  • /home/foobar/public_html/wp-content/.gk_eb46c3gc
  • /home/foobar/public_html/wp-content/.kk_cb505c82
  • /home/foobar/public_html/wp-content/ab743742.zip
  • /home/foobar/public_html/wp-content/uploads/2026/10/ab743742.zip
  • /home/foobar/public_html/wp-content/plugins/agile-beacon-go
  • /home/foobar/public_html/wp-content/plugins/hyper-proxy-cue
  • /home/foobar/public_html/wp-content/plugins/nexus-curator-rig
  • /home/foobar/public_html/wp-content/plugins/swift-keeper-dex
  • /home/foobar/public_html/wp-content/mu-plugins/.bt_hyper-proxy-cue
  • /home/foobar/public_html/wp-content/mu-plugins/.pv_hyper-proxy-cue
  • /home/foobar/public_html/wp-content/mu-plugins/.sd_hyper-proxy-cue
  • /home/foobar/public_html/wp-content/mu-plugins/.swm_hyper-proxy-cue
  • /home/foobar/public_html/wp-content/mu-plugins/hyper-proxy-cue.php
  • /home/foobar/public_html/mu-plugins/hyper-proxy-cue.php

Each compromised site received its own hash, thus the ab743742 you see above is the unique hash for this compromised site. The value re-appears in several places.

The virus infects the functions.php of the current theme, it adds a variable with the contents of the ab743742.zip file. When the site runs, the variable contents are gzdecode'ed gzinflat'ed and stored as the above zip file, which gets extracted under the plugins directory.

The virus generates a self-preserving "must-use" plugin under the mu-plugins directory.

The virus creates its own admin user in the SQL table wp_users and modifies WordPress files so that its own admin user is invisible.

Finally, the virus creates the .sc_[HASH VALUE] directory under wp-content which stores a specially crafted PHP cli script. This script is executed via exec() or other similar function, once its loaded the script deletes itself so it only remains resident in memory. This monitors all the above files and re-generates the whole infection.

At first glance, the virus tries to access a master server and receives instructions. In this case, it started adding hidden URLs inside the generated WordPress pages.

A few interesting things about this virus:

  • It uses token_get_all() to validate its own PHP syntax before making modifications to the site, so that does not corrupt the site and get noticed.
  • It uses function_exists() to test for various functions, so it can understand what it can do to its host server.
  • It generates a padding size for modified files, so that it matches the target size and avoid detection from scanners that check file size.
  • It generates various "sidecar" files with quality markers in MD5, a version file, rate limits (regenerate itself every 4 minutes) and a write race/lock flag.
  • It executes hidden commands from wp-content/.sc_[HASH VALUE}.php files that act as commands imported from its master control server.

The following strings can used to grep/search for the infection:

  • SCV:
  • SC_TH_BEGIN
  • hyper-proxy-cue
  • _sc_fam
  • _sc_commit

Let me know if you need more details!


EDIT with information about the plugin

The plugin has its own replication and cleanup code. It detects the presence of itself by looking for the string /* SCV:x.x.x */ where x.x.x is the version number of the virus. If there is a newer version then it overwrites itself.

It replicates by scanning various directories like /home, /var/www. It also modifies advanced-cache.php and object-cache.php.

The main code does:

  • List Ethereum JSON-RPC endpoints and contract addresses.
  • Receives an Ethereum contract with remote commands.
  • It sends server information like domain, plugins, admin users+passwords, session cookies, etc.
  • It receives remote code and executes it.
  • It has some obfuscated code to detect legitimate plugins, it modifies some of them.

Interestingly, it uses wp_generate_auth_cookie() to generate an admin session that sends this back to its control server, thus the attacker can login to wordpress without knowing the password of the admin.

On the front-end side, it adds Javascript code that runs its own Service Worker for intercepting web traffic.

It does various other things that I can't be bothered to write about :)


I also posted this in /r/Wordpress but the post was removed by the moderators, so I'm posting on /r/PHP to raise awareness

submitted by /u/Maria_Thesus_40 to r/PHP
[link] [comments]
  •  

Canvas: an open-source PHP framework that runs alongside your legacy code

I’ve been building Canvas, an open-source PHP framework. I built it for myself. It started because I wanted to modernize an existing PHP application incrementally and couldn’t get the integration I wanted with Symfony. Over time it grew into a standalone framework for building new applications that can also run alongside existing PHP code. I’m happily using it in my projects because it works the way I like it to, and I open sourced it so anyone who wants to can use it too.

It is deliberately similar to Symfony in some regards: it uses Symfony’s HttpFoundation, routes are annotations on controller methods, and the ORM is a Data Mapper like Doctrine. Where it differs:

  • Legacy Bridge: Canvas checks its own routes first. If it does not find a match, it will fall through to your existing PHP files. E.g. The Strangler Fig Pattern. Legacy files can use Canvas services through a canvas() helper.
  • AOP for cross-cutting concerns: instead of a middleware pipeline or kernel events, authentication, logging and caching are aspects declared with InterceptWith on a controller method or class, with before/around/after phases and priorities. Aspects are inherited, so a base controller’s aspects apply to all its subclasses.
  • Contextual dependency injection: resolve different implementations of the same interface in different situations. The context can be static, as with Laravel’s contextual bindings, but Canvas also lets you compute it per request, declared directly on the annotation.
  • ObjectQuel: a QUEL-inspired query language instead of DQL. Relationships are traversed with via, and queries that outgrow DQL, such as window functions or recursive traversal, stay in the language instead of dropping to raw SQL.

I designed it this way because I think explicit structure, clear boundaries between responsibilities, and endpoint behavior that is easy to discover make a codebase easier to work in.

I’ve also written a technical comparison with Laravel and Symfony covering routing, dependency injection, persistence, and the trade-offs involved.

If you find it interesting, I'd be happy to hear your thoughts.

submitted by /u/Big_Tadpole7174 to r/PHP
[link] [comments]
  •  
❌