Sunday, December 4, 2022

Hardening SQLite against injection in PHP

tl;dr: What are our options in php to make SQLite not write files when given malicious SQL queries as a hardening measure against SQL injection?

 

One of the most famous web application security vulnerabilities is the SQL injection.

This is where you have code like:

doQuery( "SELECT foo1, foo2 from bar where baz = '" . $_GET['fred'] . "';" );

The attacker goes to a url like ?fred='%20UNION%20ALL%20SELECT%20user%20'foo1',%20password%20'foo2'%20from%20users;--

The end result is: doQuery( "SELECT foo1, foo2 from bar where baz ='' UNION ALL SELECT user 'foo1', password 'foo2' from users ;-- ';" );

and the attacker has all your user's passwords. Portswigger has a really good detailed explanation on how such attacks work.

In addition to dumping all your private info, the usual next step is to try and get code execution. In a PHP environment, often this means getting your DB to write a a php file in the web directory.

In MariaDB/MySQL this looks like:

SELECT '<?php system($_GET["c"]);?>' INTO OUTFILE "/var/www/html/w/foo.php";

Of course, in a properly setup system, permissions are such that mysqld/mariadbd does not have permission to write in the web directory and the DB user does not have FILE privileges, so cannot use INTO OUTFILE.

In SQLite, the equivalent is to use the ATTACH command to create a new database (or VACUUM). Thus the SQLite equivalent is:

ATTACH DATABASE '/var/www/html/w/foo.php' AS foo; CREATE TABLE foo.bar (stuff text); INSERT INTO foo.bar VALUES( '<?php system($_GET["c"]);?>' );

This is harder than the MySQL case, since it involves multiple commands and you can't just add it as a suffix but have to inject as a prefix. It is very rare you would get this much control in an SQL injection.

Nonetheless it seems like the sort of thing we would want to disable in a web application, as a hardening best practice. After all, dynamically attaching multiple databases is rarely needed in this type of application.

Luckily, SQLite implements a feature called run time limits. There are a number of limits you can set. SQLite docs contain a list of suggestions for paranoid people at https://www.sqlite.org/security.html. In particular, there is a LIMIT_ATTACH which you can set to 0 to disable attaching databases. There is also a more fine grained authorizer API which allows setting a permission callback to check things on a per-statement level.

Unfortunately PHP PDO-SQLITE supports neither of these things. It does set an authorizer if you have open_basedir on to prevent reading/writing outside the basedir, but it exposes no way that I can see for you to set them yourself. This seems really unfortunate. Paranoid people would want to set runtime limits. People who have special use-cases may even want to raise them. I really wish PDO-SQLITE supported setting these, perhaps as a driver specific connection option in the constructor.

On the bright side, if instead of using the PDO-SQLITE php extension, you are using the alternative sqlite3 extension there is a solution. You still cannot set runtime limits but you can set a custom authorizer:

$db = new SQLite3($dbFileName);
$db->setAuthorizer(function ( $action, $filename ) {
        return $action === SQLite3::ATTACH ? Sqlite3::DENY : Sqlite3::OK;
});

After this if you try and do an ATTACH you get:

Warning: SQLite3::query(): Unable to prepare statement: 23, not authorized in /var/www/html/w/test.php on line 17

Thus success! No evil SQL can possibly write files.



Thursday, November 24, 2022

TV Show review: Stargate SGU


 If I could sum up this show in 2 words, I think it would be "wasted potential". There's a lot I really like about this show but the writers play it way too safe and it never really seems to come together into something truly interesting.

This was the third spin off in the Stargate Franchise, and if the original stargate TV show is Star Trek The Next Generation, and Atlantis is DS9, then this would be the franchise's Star Trek Voyager.

And it has a very similar set up to Star Trek Voyager: They get flung half way across the universe and cannot get back. They aren't really prepared for the mission. There are enemies who (eventually) get stranded with them who they don't trust but nonetheless integrate into the crew.

The other show that seems an obvious influence would be Battlestar Galactica (BSG). I'd even go as far to say that this show is essentially what would happen if Voyager and BSG had a baby that dressed up as Stargate for halloween. You can especially see the BSG influence in where the drama of the show is focused. Voyager was very much alien of the week. SGU focuses inwardly on its main cast and their struggles, in a more serialized fashion. The crew don't trust each other. There are cliques. They are stressed. They struggle with their emotions. The civilians and the military mistrust each other, just like in BSG. Additionally, BSG's Baltar was clearly an influence on how the lead scientist Dr Rush was depicted.

However, unlike BSG, this show doesn't really commit to being serialized, and as a result the characters never really grow. Any time something interesting happens to change the status quo, it gets reset in the next 2 or 3 episodes. For example, two of the character's dead girlfriend gets resurrected as a computer program in the ship - then 2 episodes later a contrived situation happens where they have to be "quarantined" in AI jail, never to be seen from or thought of again. Plots like this are common, where something happens that implies the characters will have to change and adapt, but just as you're excited to see how that plays out, the status quo ante is restored. Nothing ever seems permanent and you don't get the pay off for teased change. The worst example is probably Col. Telford, who switches from being obnoxious, to evil, to good, is marooned but comes back, is killed but then cloned in an alternate time line, etc. The character gets swapped around so much it is simply ridiculous.

In many ways, one of the best plot lines in the show, involves having an alternate timeline of the crew be sent back a thousand years, and the main timeline crew meeting their descendants and being shown archival footage of their alternate selves from a thousand years ago. This allowed the writers to show what might have been for these characters, and it was the most compelling character development in the show. I suppose the writers felt safer making bold choices with these alternate versions of the character, since the real characters didn't necessarily need to abide by them. However I can't help but think what a great show this would have been if this type of character development took place throughout.

Ultimately, this show felt like it didn't quite know what it wanted to be. It used patterns from both serialized and episodic TV shows, resulting in something that was a bit in-between which satisfied neither. It teased complex characters, but mostly failed to commit to actually developing them, instead playing things safe. Most frustrating of all, at times it did do interesting things, and you could see the potential. By the end, I really did like this characters, and wished I knew more about them. Thus why I think "wasted potential" is the best descriptor for this show.





Sunday, September 11, 2022

Why don't we ever talk about volunteer PMs in open source?

 Recently, on Wikipedia, there was an open letter to the Wikimedia Foundation, asking them to improve the New Page Patrol feature.

This started the usual debate between, WMF should do something vs It is open source, {{sofixit}} (i.e. Send a patch). There's valid points on both sides of that debate, which I don't really want to get into.

However, it occurred to me - the people on the {{sofixit}} side always suggest that people should learn how to program (an unreasonable ask), figure out how to fix something, and do it themselves. On the other hand, in a corporate environment, stuff is never done solely by developers. You usually have either a product manager or a program manager organizing the work.

Instead of saying to users - learn PHP and submit a patch, why don't we say: Be the PM for the things you want done, so a programmer can easily just do them without getting bogged down with organizational questions?

At first glance this may sound crazy - after all, ordinary users have no authority. Being a PM is hard enough when people are paid to listen to you, how could it possibly work if nobody has to listen to you. And I agree - not everything a PM does is applicable here, but i think some things are.

Some things a volunteer person could potentially do:

  • Make sure that bugs are clearly described with requirements, so a developer could just do them instead of trying to figure out what the users need
  • Make sure tasks are broken down into appropriate sized tickets
  • Make a plan of what they wish would happen. A volunteer can't force people to follow their plan, but if you have a plan people may just follow it. Too often all that is present is a big list of bugs of varying priority which is hard for a developer to figure out what is important and what isn't
    • For example, what i mean is breaking things into a few milestones, and having each milestone contain a small number (3-5) tickets around a similar theme. This could then be used in order to promote the project to volunteer developers, using language like "Help us achieve milestone 2" and track progress. Perhaps even gamifying things.
    • No plan survives contact with the enemy of course, and the point isn't to stick to any plan religiously. The point is to have a short list of what the most pressing things to work on right now are. Half the battle is figuring out what to work on and what to work on first.
  • Coordinate with other groups as needed. Sometimes work might depend on other work other people have planned to do. Or perhaps the current work is dependent on someone else's requirements (e.g. new extensions require security review). Potentially a volunteer PM could help coordinate this or help ensure that everyone is on the same page about expectations and requirements.
  • [not sure about this one] Help find constructive code reviewers. In MediaWiki development, code much be reviewed by another developer to be merged in. Finding knowledgeable people can often be difficult and a lot of effort. Sometimes this comes down to personal relationships and politely nagging people until someone bites. For many developers this is a frustrating part of the software development process. Its not clear how productive a non-developer would be here, as you may need to understand the code to know who to talk to. Nonetheless, potentially this is something a non-programmer volunteer can help with.

To use the new page patrol feature as an example - Users have a list of 56 feature requests. There's not really any indication of which ones are more important then others. A useful starting point would be to select the 3 most important. There are plenty of volunteer developers in the MediaWiki ecosystem that might work on them. The less time they have to spend figuring out what is wanted, the more likely they might fix one of the things. There are no guarantees of course, but it is a thing that someone who is not a programmer could do to move things forward.

 To be clear, being a good PM is a skill - all of this is hard and takes practice to be good at. People who have not done it before won't be good at it to begin with. But I think it is something we should talk about more, instead of the usual refrain of fix it yourself or be happy with what you got.


p.s. None of this should be taken as saying that WMF shouldn't fix anything and it should only be up to the communities, simply that there are things non-programmers could do to {{sofixit}} if they were so inclined.

 

Sunday, August 21, 2022

Weekly roundup - aug 21

 Some things i read or saw this week that i thought were interesting.

Natural perspective


I found this blog post fascinating. Basically it talks about how human preception is different than what a photograph would be. For example if there is a big object of interest in the distance it usually looks larger. I never thought much about this before,  but now that it has been pointed out to me, it rings very true. 

Kill the hero save the (narrative) world


This was a talk fron the GDC conference, by Hannah Nicklin, who is the narrative lead of a video game called Mutazione, talking about the narrative structure of video games. Essentially the speaker was arguing that many video games follow a hero's journey type of plot where the story follows a protagonist's journey to becoming a hero. They feel that this is a structure that works really well in movies: the director can direct your focus to character traits and growth. The 2 hour length is also very suitable to developing a single character's journey. They argue that video games would be better suited to follow the structure of an emsamble cast tv show. They think that this allows a better balance between players being able to do whatever they want but still getting the plot across as the focus is less on the affect of actions on the main character's phsyche and more driven by characters interacting with a community of other characters.

I know very little of video games, so i don't know how true the premise rings. However i found the reasoning quite interesting, and it gave me a lot to think about.

Galatea, Versu, Character Engine

This was a talk from 2018 by Emily Short about making non playable characters in video games feel like real people with inner lives. She also talks a bit about the pros and cons of interactive fiction, which i have always find interesting.





Saturday, August 13, 2022

Book review: Reamde

 

 

It has been a while since I have read a Stephenson novel, so when I saw this 1044 pages of light reading on the library shelf, I thought I would give it a go.

While I still enjoyed this novel, overall it was one of my least favourite Stephenson novels that I have read. I think perhaps the type of novel this was - essentially a thriller set in basically our current world - showcased Stephenson's weaknesses and hid his strengths, as a writer.

Stephenson is pretty famous for writing giant novels, and this is no exception. However usually it feels like a lot more is going on to justify the length. In this novel, the plot seems much more straight forward, and instead of using the pages to explain complex concepts or explore the world, we instead get 150 page long car/boat chases.

I think fundamentally the reason why Neal Stephenson novels are usually really fun, is their setting. He essentially writes novels set in various nerd utopias. The difference between him and most other science fiction writers writing in utopia settings, is most of those types of novels spend a lot of time arguing for the utopia or telling you how good it is. In Neal Stephenson novels, there is less of that, and the novel is more just set there. We might see how much a character likes the setting through their eyes, and how excited they are by it, but it still seems less directly aimed at the reader. I think this eases suspension of disbelief, because it feels less preachy and allows one to get absorbed in the "coolness" of the fantasy without thinking about all the ways it falls apart. For example, both Ananthem and The Big U are romanticized versions of university life (albeit romanticized in opposite directions), Cryptonomicon is a romanticized version of the cypherpunk movement, Snow crash is kind of romanticized cyberpunk. [I read these a long time ago, I might not be remembering them that well]

In this novel, the only romanticized aspect, is a world where everyone thinks MMORPGs are really cool. Unfortunately, a world where slightly more people play MMORPGs, is a lame utopia.

Additionally it has very little to do with the plot. It does provide the inciting incident, where a ransomware computer virus infecting the MMORPG encrypts some mobsters' private files. However this felt arbitrary - the MMORPG aspect really didn't matter.

Even worse, we spend hundreds of pages on this MMORPG world and the ongoing re-alignment of the in-world factions (The so-called "War of realignment"). Just as it seems to be building to something, we cut away to the main plot, never bothering to resolve or revisit what was building. This is especially sad, because in a lot of ways, it was more interesting than the main plot. Instead it is relegated to being a rather trite metaphor for a theme in the main plot about people coming together to form a chosen tribe over whatever situations they were born into.

Perhaps that's the biggest disappointment in this book: The threads don't really come together to form something bigger. There are a bunch of interesting premises at various points that don't really seem to contribute much to the overall whole. There is the design of the MMORPG/War-of-realignment, which is talked about a lot, but doesn't really go anywhere. There are the motorcycle gangsters with katanas, which are introduced as a back-story for a side character, who just ends up being shot with no katana fights. There are the main character's relatives who are some sort of fundamentalist christian gun-nut cult, but when the action comes down they mostly just hide in their house like normal people.

Why bother with all these tantalizing details when everyone in the end just acts like a normal person. Their uniqueness seems unnecessary to the novel, which feels like a major let down.

Last of all and pretty unrelatedly, the female characters felt like they were written weirdly, in a hard to define way. The main character, Zula, spent most of the novel kidnapped, and there was a bizarre amount of discussion on the differing levels of chivalry her various kidnappers showed her. As if holding the door open for her somehow makes up for the whole kidnapping at gunpoint. Several characters debate whether Yuxia is a "sister" or girlfriend material, where "friend" or "person they just met last week under very traumatic circumstances" apparently didn't cross anyone's mind. All the romantic pairings seemed kind of random and forced (Except maybe Olivia & Sokolov). Yuxia had barely even exchanged any words with Seamus before suddenly being together. Csongor and Zula also had pretty limited interaction before falling in love.

In the end, i did enjoy it, but definitely not his strongest novel.

Wednesday, July 20, 2022

Interviewed on Between the brackets

 This week I was interviewed by Yaron Karon for the second time for his MediaWiki podcast Between the Brackets.

Yaron has been doing this podcast for several years now, and I love how he highlights the different voices of all the different groups that use, interact and develop MediaWiki. He's had some fascinating people on his podcast over the years, and I highly reccomend giving it a listen.

Anyhow, it's an honour to be on the program again for episode 117. I was previously on the program 4 years ago for episode 5


Monday, July 18, 2022

Write up DiceCTF 2022: Flare and stumbling upon a Traefik 0-day (CVE-2022-23632)

 A while back, I participated in DiceCTF. During the contest I was the first person to solve the problem "Flare":

This was pretty exciting. Normally I'm happy just to be able to solve a problem — I'm never first. Even better though, my solution wasn't the intended solution, and I had instead found a 0-day in the load balancing software the contest was using!

The contest was a while ago so this post is severely belated. Nonetheless, given how exciting this all was, I wanted to write it up.

The Problem

The contest problem was short and sweet. You had a flask app behind Cloudflare. If you accessed it with an internal IP address (e.g. 192.168.0.2 or 10.0.0.2), you get the flag. The entire code is just 18 lines, mostly boilerplate:

import os
import ipaddress

from flask import Flask, request
from gevent.pywsgi import WSGIServer

app = Flask(__name__)
flag = os.getenv('FLAG', default='dice{flag}')

@app.route('/')
def index():
    ip = ipaddress.ip_address(request.headers.get('CF-Connecting-IP'))

    if isinstance(ip, ipaddress.IPv4Address) and ip.is_private:
        return flag

    return f'No flag for {format(ip)}'

WSGIServer(('', 8080), app).serve_forever()
  

Avenues of Attack

Simple to understand, but how to attack? The direct approach seems unlikely; The service is behind Cloudflare — if we can trick Cloudflare into thinking we are connecting to a site from an arbitrary IP address, then that would be a very significant vulnerability. If we could actually make a TCP connection to Cloudflare from a IP address that should not be globally routable, then something is seriously wrong with the internet.

Thinking about it, it seems like the following approaches are possible:

  • Hack Cloudflare to send the wrong CF-Connection-IP header (Seems impossible)
  • Use some sort of HTTP smuggling or header normalization issue to send something that flask thinks is a valid CF-Connection-IP header, but Cloudflare doesn't recognize, and hope that when faced with 2 conflicting headers, flask chooses ours instead of Cloudflare's. (Also seems impossible)
  • Find the backend server and connect to it directly, bypassing Cloudflare allowing us to send whatever header we want


 With that in mind, I figured it had to be the third one. After all, this is a challenge, so it must have a solution, hence I figured it's probably neither impossible nor involving a high value vulnerability in a major CDN.

I was wrong of course. The intended solution was sort of a combination of the first possibility which i dismissed as impossible and python having an "interesting" definition of an "internal" IP, which there is no way I ever would have gotten. More on that later.

Trying to Find the Backend

So now that I determined my course of action, I started hunting for the backend server. I tried googling around for snippets from the page to see if any other sites came up in google with the same text. I tried looking at various "dns history" sites that were largely useless. I tried certificate transparency logs, but no. I even tried blindly typing in variations on the challenge's domain name.

The setup for the challenges were as follows: This challenge was on https://flare.mc.ax. The other challenges were all on *.mc.ax. This was the only challenge served by Cloudflare, the rest were served directly by the CTF infrastructure using a single IP address and a wildcard certificate.

With that in mind, I thought, maybe I could connect to the IP serving the other challenges, give the flare.mc.ax SNI and host header, and perhaps I will be directly connected to the backend. So I tried that, as well as the domain fronting version where you give the wrong SNI but the right host header. This did not work. However, to my surprise instead of getting a 404 response, I got a 421 Misdirected Redirect.

421 essentially means you asked for something that this server is not configured to give you, so you should re-check your DNS resolution to make sure you have the right IP address and try again. In HTTP/2, you are allowed to reuse a connection for other domains as long as it served a TLS certificate that would work for the other domain (This is called "Connection coalescing"). However, sometimes that back-fires especially with wildcard certs. Just because a server serves a TLS certificate for *.example.com, doesn't mean it knows how to literally handle everything under example.com since some subdomains might be served by a different server on a different IP. The new error code was created for such cases, to tell the browser it should stop with the connection coalescing, look up the DNS records for the domain name again, and open a separate connection. We needed a new code, because if the server just responded with a 404, the browser wouldn't know if its because the page just doesn't exist, or if its because the connection was inappropriately coalesced.

Looking back, I'm not sure I should have seen this as a sign. After all I was asking for a domain name that this server did not serve but had a correct certificate for, so this was the appropriate HTTP status code to give. However, the uniqueness of the error code and sudden change in behaviour around the domain name I was interested in, made me feel like I was on to something.

So I tried messing around with variations in headers and SNI. I tried silly things like having Host: flare.ac.mx/foo in the hopes that it would maybe confuse one layer, but another layer would strip it off, and get me the site i wanted or something like that.

Why settle for partial qualification? 

Eventually I tried Host: flare.ac.mx. (note the extra dot at the end) with no SNI.

curl 'https://104.196.60.107'  -vk --header 'host: flare.mc.ax.' --http1.1 --header 'CF-Connecting-IP: 127.0.0.1' --header 'X-Forwarded-For: 127.0.0.1' --header 'CF-ray: REDACTED' --header 'CF-IPCountry: CA' --header 'CF-Visitor: {"scheme":"https"}' --header 'CDN-Loop: cloudflare'

It worked.

Wait what?

What does a dot at the end of a domain name even mean?

In DNS, there is the concept of a partially qualified domain name (PQDN), and its opposite, the fully qualified domain name (FQDN). A partially qualified domain name is similar to a relative path - you can setup a default DNS search path in your DNS config (usually set by DHCP) that acts as a default suffix for partially qualified domain names. For example, if your default search path is example.com and you look up the host foo DNS will check foo.example.com.

I imagine this made more sense during the early internet, when it was more a "network of networks", and it was more common that you wanted to look up local resources on your local network.

In DNS, there is the root zone, which is represented by the empty string. This is the top of the DNS tree, which has TLDs like com or net, as its children.

If you add a period to the end of your DNS name, for example foo., this tells the DNS software that it isn't a partially qualified DNS name, but what you actually want, is to look up the foo domain in the root zone. So it does not lookup foo.example.com., but instead just foo..

For the most part, this is an obscure bit of DNS trivia. However, as far as web browsers are concerned, the PQDN example.net and FQDN example.net. are entirely separate domains. The same origin policy treats them differently, cookies set to one do not affect the other (TLS certificates seem to work for both though). In the past, people have used this trick to avoid advertisers on some websites.

So why did this work

So at this point, I solved the puzzle, obtained the flag and submitted it. Yay me!
 
But I still wasn't really sure what happened. I assumed there was some sort of misconfiguration involved, but I wasn't sure what. I did not have the configuration of the load-balancer. For that matter, I wasn't even sure yet which load balancing software was in use.

After I solved the problem, the competition organizers reached out and asked me what method I use. I imagine they wanted to see if I found the intended solution or stumbled upon something else. When I told them, they were very surprised. Apparently mutual TLS had been set up with cloudflare, and it should have been impossible to contact the backend if you did not have the correct client certificate, which I did not.

Wait what!?

The load balancing software in question was Traefik. In it, you can configure various requirements for different hosts. For example, you can say that a certain host needs a specific version of TLS, specific ciphers, specific server certificate or even a specific client certificate (mTLS). There is also a section for default options. In this case, they had one set of TLS settings for most of the domains, and a rule for the flare domain that you needed the correct client certificate to get access.

In normal operation, the SNI is checked and the appropriate requirements are applied. In the event that the SNI doesn't match the host header, and the host header matches a domain with different TLS requirements then the default requirements, a 421 status code is sent. This is all good.

However, if the host header has a period at the end to make it a FQDN, the code checking the TLS requirements doesn't think it matches the non-period version, so only the default requirements apply. However the rest of the code will still forward the request to the correct domain and process it as normal.

Thus, you can bypass any domain specific security requirements by not sending the SNI and adding an extra period to the host header.

This would be one thing for settings like min TLS version. However, it is an entirely different thing for access control settings such as mutual TLS as it allows an attacker to fully bypass the required client certificate, getting access to things they shouldn't be able to.

I reported this to the Traefik maintainers, and they fixed the issue in version 2.6.1. It was assigned CVE-2022-23632 and you can read more about it in their advisory. This was pretty exciting as well, as Traefik is used by quite a few people, and based on their github security advisory page, this is the first high-severity vulnerability that has been found in it.

What was the intended solution?

I found out later from the organizers of the CTF, that the intended solution was something very different. I definitely would never have came up with this myself.

The intended solution was to exploit two weird behaviours:

  • The python ipaddress library considers class E IP addresses (i.e. 240.0.0.0/4) to be "private". Class E addresses are reserved for future use. They are not globally routable, but they aren't private either, so it is odd that python considers them private. Python's own docs say that is_private is "True if the address is allocated for private networks" linking to IANA's official list, even though 240.0.0.0/4 is not listed as private use on that list.
  • Cloudflare has a feature where if your site does not support IPv6, you can enable "Pseudo IPv4", where the ipv6 connections will be forwarded as if they come from a class E IPv4 address. Cloudflare talks more about it in their blog post.

Which is a pretty fascinating combination.

Initially I discarded the possibility you could make Cloudflare give the wrong IP address, because I thought that would be such a major hack, that it wouldn't show up in this type of contest; people would either be reporting it or exploiting it, depending on the colour of their hat. However, my assumption was based on the idea that any sort of exploit would let you pick your IP. Being able to present as a random class E (which class E IP is based on an md5 hash of the top 64 bits of your IPv6 address, so you cannot choose it), is no where near as useful as being able to chose your IP (Everyone is just so trusting of 127.0.0.1). While this is a fascinating challenge, its hard to imagine a non-contrived situation where this would be a useful primitive. Making network access control in the real world that just blacklists all globally routable IPs instead of your own network seems silly. Even sillier would be to whitelist class E for some reason. Sure I guess an attacker could masquerade as one of these class E addresses to confuse anti-abuse systems, but if the site properly processes those connections, then it seems anti-abuse systems are likely to handle them just as easily as a normal IP. Since its still tied to your real IP, you can't hop between them unless you can hop between real IPs. If it ever really became an issue, Cloudflare lets you disable them, and there is also an additional header with the original IPv6 address you can use. At worst, maybe it makes reading logs after an incident more complicated, but this would be a really bad way to hide yourself in a world where VPNs are cheap and tor is free. In conclusion - its a fascinating behaviour, but practically speaking doesn't seem exploitable in the way that "make Cloudflare report your ip is something other than your ip" sounds like it would be exploitable at first glance.