synthetic garden

so-called blog simply screams about various topics. no known way to shut it off

Garry's Mod in procexp, attempting to access a DLL from a network share. Yup, that's me. You're probably wondering how Garry's Mod got there. Well, it's quite simple actually.

Here's the simplistic explanation: With steam:// URLs, rungameid allows you to pass console arguments. I use one of those to tell Garry's Mod its files are on a remote server. It dutifully loads them, DLLs and all.

But how exactly does this work? Well, it's quite dumb actually. You see, normal Windows file operations contain a secret hidden trapdoor in them called “network shares.” Anyway, you can host them for other people using a protocol called WebDAV. This allows you to load files from across the net using a simple command, which is nice for you if you're the small percentage of users who want this in their daily lives.

However, it can lead to a lot of mischief. That little tidbit may, if you have been around the infosec community, remind you of a certain thing called “pass-the-hash.” (For the record, I'm 99% sure you can do pass-the-hash with this. I think it would be fair to question your reasoning, but you can.) Here, we use it to set the working game directory for the Source Engine to a remote share, causing it to load info remotely.

Valve, in their infinite wisdom, made it so that you can't use more than two backslashes in rungameid. Why is this the limit? No clue. However, it's easy to bypass by just...having all your stuff at the root folder, which is a thing you're allowed to do.

So, start a WebDAV server and point -game to it in your GMod instance. It takes a bit, and then immediately complains about gameinfo.txt not existing.

So. Start a WebDAV server, copy gameinfo.txt from your GMod folder into the root of the WebDAV folder, and then sit on your butt and watch Procmon. Garry's Mod immediately starts flipping the fuck out, filling your screen with pure white. That's great. Kill it, and then check your logs. You'll find a ton of DLLs that it loads — picking any of the ones in bin/ named game_shader_dx█.dll will immediately get your DLL loaded, word of the wise.

Any DLL that runs anything on DLL_PROCESS_ATTACH or such will immediately run their payloads.

Now, here's the fun part: I know for a fact that last I checked, this could be vaguely replicated in CS:GO. But I don't wanna install it, and I don't wanna check. If you figure it out, props to you, and maybe report it to Valve so they'll actually start doing something about this because it sucks man lol

How to mitigate, for the layman:

Don't open any steam:// links that look long, don't open steam from a browser, open your games directly from the Steam interface if you wanna do anything.

How to mitigate, for Valve:

Either breaking change rungameid, push out a critical engine update that doesn't allow -game to load from a network share, or be exceedingly lazy and start stripping two backslashes instead of three. Also, please don't ban me.

Vendor Notification Timeline:

  • June 13, 2018: Reported
  • June 13, 2018: Report accepted
  • July 23, 2018: Asked for updates
  • March 8, 2019: Received apology for late reply
  • July 19, 2019: Severity was downgraded from 8.0 to 4.3 due to “no actual RCE”, $750 bounty awarded
  • October 4th, 2019: Asked for update
  • March 1st, 2021: Posted this blog

Once upon a time, there was a site where there was a ton of JS crud. Unfortunately, some of that crud was “loading images” (despite the fact that there was no reason to, just like there isn't 99% of the time)

So, I wanted to make something that'd let data-src be converted to src quick and easy. I found a stackoverflow answer and figured “wwelp, that's kinda large but it'll do”. Then I thought “meh, could compress it a little”.

var imgEl = document.getElementsByTagName('img');
for (var i=0; i<imgEl.length; i++) {
    if(imgEl[i].getAttribute('data-src')) {
       imgEl[i].removeAttribute('data-src'); //use only if you need to remove data-src attribute after setting src

// 324 characters

This was obviously non-optimal, and the first thing that caught my eye was that comment. We don't need to remove, so out that goes. I also changed the variable names to try and compress a little before I sent it through a minifier.

var i = document.getElementsByTagName('img');
for (var x=0; x<i.length; x++) {
    if(i[x].attributes['data-src']) {

// 188 characters

This was Good Enough For Now™, so I sent it through uglify-js. (Well, some site that used it. Too lazy to npm install.)

for(var i=document.getElementsByTagName("img"),x=0;x<i.length;x++)i[x].getAttribute("data-src")&&i[x].setAttribute("src",i[x].getAttribute("data-src"));

// 152 characters

This cut down the obvious cruft, still needed a little something. Why were we grabbing data-src twice? I changed the left side to set a variable while checking for its validness.

for(var i=document.getElementsByTagName("img"),x=0;x<i.length;x++)(_=i[x].getAttribute("data-src"))&&i[x].setAttribute("src",_);

// 128 characters

Better. But still not good enough. I noticed something: that for-loop took up about half of the code! (66 characters, to be exact)

Obviously, there was a better way — I knew .forEach was a thing, but unfortunately, getElementsByTagName didn't support it by default because it used HTMLCollection (pls fix) so I had to use Array.from.


// 117 characters

I immediately scanned for a way to not use Array.from, and just my luck — querySelectorAll used NodeList, which did support it. (Unless you were on IE, in which case: tough luck)


// 106 characters

Then I realized something. “Wait, does dataset return strings?” I knew attributes didn't, but it was work a try. ...And return strings it did!


// 96 characters

Double digits, baby! ...I then realized that despite that my JS console hadn't been showing querySelectorAll as an attribute of document, it worked just fine. Oops.


// 91 characters

And then I realized “oh wow, I can just set src instead of setAttribute, I am a fool” (technically the stackoverflow answer was the fool, but I was too for not noticing)

Unfortunately, that required me giving something up.

You see, the original compression had been using && in place of if. But if you try to do an assignment on the right hand of an &&, that's invalid. So I had to go back to using if. But as well, using if inside of an arrow function is also incorrect, unless you surround it with brackets.

Fortunately, this still resulted in a net gain. (Or net loss of characters, as it was.)


// 77 characters

And it was still fairly comprehensible after all was said and done! From 324 characters to 77. Whew. That's almost 75% of the length shaved off!

That's...about how I shorten code down. I don't know why I needed to document this but hopefully it helps someone with something. Have fun!

How 7 proxies can't help much when you don't know what you're doing

The seven proxies meme but he's frowning. The text has been replaced with WHY IS UNDER ATTACK MODE CAUSING MY SERVER TO BREAK WTF.

So, presume you have a VPN. You use this VPN to go around posting on piracy forums and such and keeping up with the latest leaks. Suddenly, one day, you get a cease and desist. But you had a VPN! What happened?

Well, what you didn't realize is that BitTorrent is a decentralized protocol where your client sends the IP to a tracker that lets people know where to send the data. (It's more complex than that, but just work with me here.)
The issue is your client didn't know you were behind a VPN and dutifully sent your home IP while you were getting that fresh leaked copy of Shrek 2, meaning people watching the torrent could see your IP too.

Thankfully, VPNs are for sending requests too, so this is an easy to solve problem!


ActivityPub is a decentralized protocol that runs on a server and makes external requests.
Cloudflare is a protection service that assumes you will not be making external requests and only will be replying to requests.

Please, stop trying to combine them. As part of my plea, here's proof that this doesn't work and you should stop.

import requests
import argparse

parser = argparse.ArgumentParser(description='Cause a GET request from a Activity-Pub compatible instance.')
parser.add_argument('instance', help="Hostname of the instance.")
parser.add_argument('url', help="URL to request.")
parser.add_argument('-d', '--debug', action='store_true', help="Enable debug output")
args = parser.parse_args()
if args.debug:
  # bunch of bullshit to enable requests logging
  import logging
  import http.client as http_client
  http_client.HTTPConnection.debuglevel = 1
  requests_log = logging.getLogger("requests.packages.urllib3")
  requests_log.propagate = True
if '://' not in args.instance:
  inbox = f"https://{args.instance}/inbox"
  inbox = f"{args.instance}/inbox"
header = 'keyId="%s",headers="(request-target) host",signature="aA=="' % (args.url)

p =, data = "{}", headers = {"Signature": header, "User-Agent": "http.rb/3.3.0 ()"})
if "Public key not found" in p.text: 
  print("[!] (Public key not found usually means it worked, check your logs)")
elif p.status_code == 500: 
  print("[!] (500 occurred, if Pleroma, check logs!)")
  print("[?] Not sure what this is, but check logs anyway lol")

This script will automatically pass a lousy, really effortless request to an instance that makes it dutifully request whatever you ask of it. Including IP loggers, which are a dime a dozen, meaning I can get your instance's IP anyway. For example, Spinster is at

Now that I have sufficiently proven that any reverse proxy without an actual proxy is useless, what can you do about it?

Don't use Cloudflare

Seriously, Cloudflare blocks automated requests.
The way ActivityPub works is automated requests.

Please stop using CloudFlare. If you want an anti-DDoS solution, get an anti-DDoS server host.

Thank you.