收集 TeamViewer 記錄

支援的國家/地區:

本文說明如何使用 Amazon S3,將 TeamViewer 記錄擷取至 Google Security Operations。剖析器會從 JSON 格式的記錄中擷取稽核事件。這項服務會逐一檢查活動詳細資料,將特定屬性對應至整合式資料模型 (UDM) 欄位、處理參與者和簡報者資訊,並根據使用者活動將活動分類。剖析器也會執行資料轉換,例如合併標籤,以及將時間戳記轉換為標準格式。

事前準備

請確認您已完成下列事前準備事項:

  • Google SecOps 執行個體。
  • TeamViewer 的特殊存取權。
  • AWS 的特殊權限 (S3、Identity and Access Management (IAM)、Lambda、EventBridge)。

取得 TeamViewer 的必要條件

  1. 以管理員身分登入 TeamViewer 管理主控台
  2. 依序前往「我的個人資料」>「應用程式」
  3. 點選「Create app」(建立應用程式)
  4. 提供下列設定詳細資料:
    • 應用程式名稱:輸入描述性名稱 (例如 Google SecOps Integration)。
    • 說明:輸入應用程式的說明。
    • 權限:選取稽核記錄存取權限。
  5. 按一下「建立」,然後將產生的 API 憑證儲存在安全地點。
  6. 記錄 TeamViewer API 基礎網址 (例如 https://webapi.teamviewer.com/api/v1)。
  7. 複製下列詳細資料並存放在安全位置:
    • CLIENT_ID
    • CLIENT_SECRET
    • API_BASE_URL

為 Google SecOps 設定 AWS S3 值區和 IAM

  1. 按照這份使用者指南建立 Amazon S3 bucket建立 bucket
  2. 儲存 bucket 的「名稱」和「區域」,以供日後參考 (例如 teamviewer-logs)。
  3. 請按照這份使用者指南建立使用者建立 IAM 使用者
  4. 選取建立的「使用者」
  5. 選取「安全憑證」分頁標籤。
  6. 在「Access Keys」部分中,按一下「Create Access Key」
  7. 選取「第三方服務」做為「用途」
  8. 點選「下一步」
  9. 選用:新增說明標記。
  10. 按一下「建立存取金鑰」
  11. 按一下「下載 CSV 檔案」,儲存「存取金鑰」和「存取金鑰密碼」,以供日後參考。
  12. 按一下 [完成]
  13. 選取「權限」分頁標籤。
  14. 在「權限政策」部分中,按一下「新增權限」
  15. 選取「新增權限」
  16. 選取「直接附加政策」
  17. 搜尋「AmazonS3FullAccess」AmazonS3FullAccess政策。
  18. 選取政策。
  19. 點選「下一步」
  20. 按一下「Add permissions」。

設定 S3 上傳的身分與存取權管理政策和角色

  1. AWS 控制台中,依序前往「IAM」>「Policies」(政策)
  2. 按一下「建立政策」>「JSON」分頁
  3. 複製並貼上下列政策。
  4. 政策 JSON (如果您輸入的 bucket 名稱不同,請替換 teamviewer-logs):

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::teamviewer-logs/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::teamviewer-logs/teamviewer/audit/state.json"
        }
      ]
    }
    
  5. 依序點選「下一步」>「建立政策」

  6. 依序前往「IAM」>「Roles」>「Create role」>「AWS service」>「Lambda」。

  7. 附加新建立的政策。

  8. 為角色命名 TeamViewerToS3Role,然後按一下「建立角色」

建立 Lambda 函式

  1. AWS 控制台中,依序前往「Lambda」>「Functions」>「Create function」
  2. 按一下「從頭開始撰寫」
  3. 請提供下列設定詳細資料:

    設定
    名稱 teamviewer_to_s3
    執行階段 Python 3.13
    架構 x86_64
    執行角色 TeamViewerToS3Role
  4. 建立函式後,開啟「程式碼」分頁,刪除存根並貼上以下程式碼 (teamviewer_to_s3.py)。

    #!/usr/bin/env python3
    # Lambda: Pull TeamViewer audit logs and store raw JSON payloads to S3
    # - Time window via {FROM}/{TO} placeholders (UTC ISO8601), URL-encoded.
    # - Preserves vendor-native JSON format for audit and session data.
    # - Retries with exponential backoff; unique S3 keys to avoid overwrites.
    
    import os, json, time, uuid, urllib.parse
    from urllib.request import Request, urlopen
    from urllib.error import URLError, HTTPError
    
    import boto3
    
    S3_BUCKET   = os.environ["S3_BUCKET"]
    S3_PREFIX   = os.environ.get("S3_PREFIX", "teamviewer/audit/")
    STATE_KEY   = os.environ.get("STATE_KEY", "teamviewer/audit/state.json")
    WINDOW_SEC  = int(os.environ.get("WINDOW_SECONDS", "3600"))  # default 1h
    HTTP_TIMEOUT= int(os.environ.get("HTTP_TIMEOUT", "60"))
    API_BASE_URL = os.environ["API_BASE_URL"]
    CLIENT_ID   = os.environ["CLIENT_ID"]
    CLIENT_SECRET = os.environ["CLIENT_SECRET"]
    MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3"))
    USER_AGENT  = os.environ.get("USER_AGENT", "teamviewer-to-s3/1.0")
    
    s3 = boto3.client("s3")
    
    def _load_state():
        try:
            obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            return json.loads(obj["Body"].read())
        except Exception:
            return {}
    
    def _save_state(st):
        s3.put_object(
            Bucket=S3_BUCKET,
            Key=STATE_KEY,
            Body=json.dumps(st, separators=(",", ":")).encode("utf-8"),
            ContentType="application/json",
        )
    
    def _iso(ts: float) -> str:
        return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(ts))
    
    def _get_access_token() -> str:
        # OAuth2 Client Credentials flow for TeamViewer API
        token_url = f"{API_BASE_URL.rstrip('/')}/oauth2/token"
        data = urllib.parse.urlencode({
            'grant_type': 'client_credentials',
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET
        }).encode('utf-8')
    
        req = Request(token_url, data=data, method="POST")
        req.add_header("Content-Type", "application/x-www-form-urlencoded")
        req.add_header("User-Agent", USER_AGENT)
    
        with urlopen(req, timeout=HTTP_TIMEOUT) as r:
            response = json.loads(r.read())
            return response["access_token"]
    
    def _build_audit_url(from_ts: float, to_ts: float, access_token: str) -> str:
        # Build URL for TeamViewer audit API endpoint
        base_endpoint = f"{API_BASE_URL.rstrip('/')}/reports/connections"
        params = {
            "from_date": _iso(from_ts),
            "to_date": _iso(to_ts)
        }
        query_string = urllib.parse.urlencode(params)
        return f"{base_endpoint}?{query_string}"
    
    def _fetch_audit_data(url: str, access_token: str) -> tuple[bytes, str]:
        attempt = 0
        while True:
            req = Request(url, method="GET")
            req.add_header("User-Agent", USER_AGENT)
            req.add_header("Authorization", f"Bearer {access_token}")
            req.add_header("Accept", "application/json")
    
            try:
                with urlopen(req, timeout=HTTP_TIMEOUT) as r:
                    return r.read(), (r.headers.get("Content-Type") or "application/json")
            except (HTTPError, URLError) as e:
                attempt += 1
                print(f"HTTP error on attempt {attempt}: {e}")
                if attempt > MAX_RETRIES:
                    raise
                # exponential backoff with jitter
                time.sleep(min(60, 2 ** attempt) + (time.time() % 1))
    
    def _put_audit_data(blob: bytes, content_type: str, from_ts: float, to_ts: float) -> str:
        # Create unique S3 key for audit data
        ts_path = time.strftime("%Y/%m/%d", time.gmtime(to_ts))
        uniq = f"{int(time.time()*1e6)}_{uuid.uuid4().hex[:8]}"
        key = f"{S3_PREFIX}{ts_path}/teamviewer_audit_{int(from_ts)}_{int(to_ts)}_{uniq}.json"
    
        s3.put_object(
            Bucket=S3_BUCKET, 
            Key=key, 
            Body=blob, 
            ContentType=content_type,
            Metadata={
                'source': 'teamviewer-audit',
                'from_timestamp': str(int(from_ts)),
                'to_timestamp': str(int(to_ts))
            }
        )
        return key
    
    def lambda_handler(event=None, context=None):
        st = _load_state()
        now = time.time()
        from_ts = float(st.get("last_to_ts") or (now - WINDOW_SEC))
        to_ts = now
    
        # Get OAuth2 access token
        access_token = _get_access_token()
    
        url = _build_audit_url(from_ts, to_ts, access_token)
        print(f"Fetching TeamViewer audit data from: {url}")
    
        blob, ctype = _fetch_audit_data(url, access_token)
    
        # Validate that we received valid JSON data
        try:
            audit_data = json.loads(blob)
            print(f"Successfully retrieved {len(audit_data.get('records', []))} audit records")
        except json.JSONDecodeError as e:
            print(f"Warning: Invalid JSON received: {e}")
    
        key = _put_audit_data(blob, ctype, from_ts, to_ts)
    
        st["last_to_ts"] = to_ts
        st["last_successful_run"] = now
        _save_state(st)
    
        return {
            "statusCode": 200,
            "body": {
                "success": True, 
                "s3_key": key, 
                "content_type": ctype,
                "from_timestamp": from_ts,
                "to_timestamp": to_ts
            }
        }
    
    if __name__ == "__main__":
        print(lambda_handler())
    
  5. 依序前往「設定」>「環境變數」

  6. 依序點選「編輯」> 新增環境變數

  7. 輸入下表提供的環境變數,並將範例值換成您的值。

    環境變數

    範例值
    S3_BUCKET teamviewer-logs
    S3_PREFIX teamviewer/audit/
    STATE_KEY teamviewer/audit/state.json
    WINDOW_SECONDS 3600
    HTTP_TIMEOUT 60
    MAX_RETRIES 3
    USER_AGENT teamviewer-to-s3/1.0
    API_BASE_URL https://webapi.teamviewer.com/api/v1
    CLIENT_ID your-client-id (步驟 2)
    CLIENT_SECRET your-client-secret (步驟 2)
  8. 建立函式後,請留在函式頁面 (或依序開啟「Lambda」>「Functions」>「your-function」)。

  9. 選取「設定」分頁標籤。

  10. 在「一般設定」面板中,按一下「編輯」

  11. 將「Timeout」(逾時間隔) 變更為「5 minutes (300 seconds)」(5 分鐘 (300 秒)),然後按一下「Save」(儲存)

建立 EventBridge 排程

  1. 依序前往「Amazon EventBridge」>「Scheduler」>「Create schedule」
  2. 提供下列設定詳細資料:
    • 週期性時間表費率 (1 hour)。
    • 目標:您的 Lambda 函式 teamviewer_to_s3
    • 名稱teamviewer-audit-1h
  3. 按一下「建立時間表」

(選用) 為 Google SecOps 建立唯讀 IAM 使用者和金鑰

  1. 依序前往 AWS 管理中心 > IAM >「Users」(使用者) >「Add users」(新增使用者)
  2. 點選 [Add users] (新增使用者)。
  3. 提供下列設定詳細資料:
    • 使用者:輸入 secops-reader
    • 存取類型:選取「存取金鑰 - 程式輔助存取」
  4. 按一下「建立使用者」
  5. 附加最低讀取權限政策 (自訂):依序選取「Users」(使用者) >「secops-reader」>「Permissions」(權限) >「Add permissions」(新增權限) >「Attach policies directly」(直接附加政策) >「Create policy」(建立政策)
  6. JSON:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::teamviewer-logs/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::teamviewer-logs"
        }
      ]
    }
    
  7. Name = secops-reader-policy

  8. 依序點選「建立政策」> 搜尋/選取 >「下一步」>「新增權限」

  9. secops-reader 建立存取金鑰:依序點選「安全憑證」>「存取金鑰」

  10. 按一下「建立存取金鑰」

  11. 下載 CSV。(您會將這些值貼到動態饋給中)。

在 Google SecOps 中設定動態饋給,擷取 TeamViewer 記錄

  1. 依序前往「SIEM 設定」>「動態饋給」
  2. 按一下「+ 新增動態消息」
  3. 在「動態饋給名稱」欄位中輸入動態饋給名稱 (例如 TeamViewer logs)。
  4. 選取「Amazon S3 V2」做為「來源類型」
  5. 選取「TeamViewer」做為「記錄類型」
  6. 點選「下一步」
  7. 指定下列輸入參數的值:
    • S3 URIs3://teamviewer-logs/teamviewer/audit/
    • 來源刪除選項:根據偏好設定選取刪除選項。
    • 檔案存在時間上限:包含在過去天數內修改的檔案。預設值為 180 天。
    • 存取金鑰 ID:具有 S3 值區存取權的使用者存取金鑰。
    • 存取密鑰:具有 S3 bucket 存取權的使用者私密金鑰。
    • 資產命名空間資產命名空間
    • 擷取標籤:套用至這個動態饋給事件的標籤。
  8. 點選「下一步」
  9. 在「完成」畫面中檢查新的動態饋給設定,然後按一下「提交」

UDM 對應表

記錄欄位 UDM 對應 邏輯
AffectedItem metadata.product_log_id 原始記錄中的 AffectedItem 值會直接對應至這個 UDM 欄位。
EventDetails.NewValue principal.resource.attribute.labels.value 如果 PropertyName 包含 (server),則 NewValue 會做為 principal.resource.attribute.labels 中標籤的值。
EventDetails.NewValue principal.user.user_display_name 如果 PropertyNameName of participant,則 NewValue 會做為主體的顯示名稱。
EventDetails.NewValue principal.user.userid 如果 PropertyNameID of participant,則 NewValue 會做為主體的使用者 ID。
EventDetails.NewValue security_result.about.labels.value 對於所有其他 PropertyName 值 (特定條件處理的值除外),NewValue 會做為 security_result.about.labels 陣列中標籤的值。
EventDetails.NewValue target.file.full_path 如果 PropertyNameSource file,則 NewValue 會做為目標檔案的完整路徑。
EventDetails.NewValue target.resource.attribute.labels.value 如果 PropertyName 包含 (client),則 NewValue 會做為 target.resource.attribute.labels 中標籤的值。
EventDetails.NewValue target.user.user_display_name 如果 PropertyNameName of presenter,系統會剖析 NewValue。如果是整數,系統會捨棄。否則,系統會將其做為目標的使用者顯示名稱。
EventDetails.NewValue target.user.userid 如果 PropertyNameID of presenter,則 NewValue 會做為目標的使用者 ID。
EventDetails.PropertyName principal.resource.attribute.labels.key 如果 PropertyName 包含 (server),則 PropertyName 會做為 principal.resource.attribute.labels 中標籤的鍵。
EventDetails.PropertyName security_result.about.labels.key 對於所有其他 PropertyName 值 (特定條件處理的值除外),PropertyName 會做為 security_result.about.labels 陣列中標籤的鍵。
EventDetails.PropertyName target.resource.attribute.labels.key 如果 PropertyName 包含 (client),則 PropertyName 會做為 target.resource.attribute.labels 中標籤的鍵。
EventName metadata.product_event_type 原始記錄中的 EventName 值會直接對應至這個 UDM 欄位。
Timestamp metadata.event_timestamp 系統會剖析原始記錄中的 Timestamp 值,並做為中繼資料中的事件時間戳記。如果 src_user (衍生自 ID of participant) 不為空白,請設為 USER_UNCATEGORIZED,否則請設為 USER_RESOURCE_ACCESS。硬式編碼為 TEAMVIEWER。硬式編碼為 TEAMVIEWER。硬式編碼為 TEAMVIEWER

還有其他問題嗎?向社群成員和 Google SecOps 專業人員尋求答案。