← CCSP: segurança cloud, dados e operação
19 / 26 · 55 MIN

Incidentes cloud: cronologia e evidência

Constrói uma cronologia com limites explícitos e coordena contenção, evidência e recuperação.

Prepara decisões antes da madrugada do incidente

Num serviço fictício de fundos, uma credencial efetua exportações invulgares pouco antes do fecho. O alerta é uma observação a triar; não identifica sozinho uma pessoa ou toda a extensão do incidente. Define quem declara o incidente, autoriza contenção, coordena o fornecedor e comunica com o negócio. Prepara acessos de emergência controlados, contactos alternativos e critérios de escalada antes de precisar deles. A referência NIST SP800-61 Rev.3, final de abril de 2025, integra resposta a incidentes na gestão de risco e substitui a Rev.2. Usa-a para estruturar decisões e responsabilidades, sem transformar o documento em regras internas do banco. O objetivo operacional é limitar impacto com registo do que se sabe, do que se supõe e do que falta confirmar.

Preserva o original e documenta a aquisição

O laboratório cria um ficheiro JSONL fictício, copia os bytes para uma aquisição local e regista origem declarada, tamanho, SHA-256, coletor e hora da cópia. A análise usa essa cópia e verifica que o original não mudou. Isso demonstra operações locais concretas, não uma aquisição forense de um sistema comprometido. Um hash igual suporta igualdade com a referência guardada; não prova que os eventos eram verdadeiros antes da recolha. O ensaio altera o ficheiro e depois mostra que recalcular um manifesto igualmente editável faz desaparecer a divergência. Na operação real, preserva proveniência, acessos, transferências e procedimentos de custódia apropriados ao caso. Evita colocar dados sensíveis integrais num canal de incidente com participantes não autorizados.

Distingue hora do evento, receção e ordem causal

Nos dados fictícios, 11:00 com offset +01:00 corresponde a 10:00 UTC. A utilização da credencial só chega ao coletor às 10:04, depois de uma resposta aplicacional recebida às 10:01:10. Ordenar por receção produziria uma narrativa diferente de ordenar pela hora declarada do evento. Um agente declara 10:06 para um registo recebido às 10:02: o atraso calculado é negativo, quatro minutos, e merece análise de relógio ou metadados. Não reescrevas o original para tornar a sequência conveniente. O laboratório recusa timestamps sem fuso e assinala a anomalia. Mesmo depois de normalizar UTC, intervalos de erro sobrepostos impedem afirmar ordem estrita. Request IDs e proximidade temporal ajudam a investigar; não demonstram causalidade ou autoria humana por si.

Correlaciona sem apagar contradições

Existem seis registos brutos e cinco eventos distintos no exemplo. Um evento é entregue duas vezes com horas de receção diferentes. O laboratório agrupa pela combinação de conta, fonte e eventId, preservando ambos os registos no original. O mesmo eventId noutra conta não é descartado. Uma variante muda a ação de um evento com a mesma identidade: o analisador sinaliza conflito, conserva o primeiro para inspeção e exige revisão da linha recusada antes de concluir. Não trata a contradição como duplicado benigno. Trinta verificações passaram em duas execuções locais. Não houve SIEM, compromisso real ou validação CloudTrail. No CloudTrail, ativar entrega de digests não executa automaticamente a validação da integridade; a documentação distingue essas operações.

Conter não é encerrar o incidente

A contenção deve considerar impacto, rapidez, permissões e possibilidade de preservar evidência. Se houver dano em curso, não adies toda a resposta apenas para obter uma recolha perfeita; coordena ações autorizadas e regista o compromisso tomado. Isolar uma instância não demonstra que credenciais copiadas deixaram de funcionar noutros recursos. Recuperar o endpoint também não prova que foram removidas persistência e causas exploradas. Define critérios para retorno ao serviço, validação funcional, monitorização reforçada e ações de melhoria. Comunica factos confirmados e incertezas com próxima atualização marcada. Obrigações de notificação dependem do contexto e devem ser avaliadas pelos responsáveis apropriados; esta aula não inventa um prazo universal. Resumo: preserva, correlaciona com limites, contém segundo autoridade e valida a recuperação.

"""Original local incident-analysis exercise. All events and clock bounds are fictional."""
import datetime as dt
import hashlib
import json
from pathlib import Path
import shutil
import sys
import tempfile


def digest(data):
    return hashlib.sha256(data).hexdigest()


def timestamp(value):
    if not isinstance(value, str):
        raise ValueError('timestamp type')
    parsed = dt.datetime.fromisoformat(value.replace('Z', '+00:00'))
    if parsed.tzinfo is None or parsed.utcoffset() is None:
        raise ValueError('timezone required')
    return parsed.astimezone(dt.timezone.utc)


def analyze(data):
    unique, duplicates, rejected = {}, [], []
    for lineno, line in enumerate(data.splitlines(), 1):
        try:
            row = json.loads(line)
            if not isinstance(row, dict):
                raise ValueError('object required')
            for field in ('account', 'source', 'eventId', 'requestId', 'action'):
                if not isinstance(row.get(field), str) or not row[field]:
                    raise ValueError('missing identity field')
            event = timestamp(row['eventTime'])
            received = timestamp(row['receivedTime'])
            key = (row['account'], row['source'], row['eventId'])
            # Preserve receive timestamps separately; compare producer event fields.
            event_fields = {k: v for k, v in row.items() if k != 'receivedTime'}
            canonical = json.dumps(event_fields, sort_keys=True, separators=(',', ':'))
            if key in unique:
                if canonical != unique[key]['canonical']:
                    raise ValueError('conflicting event identity')
                duplicates.append({'line': lineno, 'identity': list(key), 'receivedTime': row['receivedTime']})
                continue
            unique[key] = {'row': row, 'line': lineno, 'canonical': canonical,
                           'eventUTC': event.isoformat(), 'receivedUTC': received.isoformat(),
                           'delaySeconds': (received-event).total_seconds(),
                           'clockWarning': received < event}
        except (ValueError, KeyError, TypeError) as error:
            rejected.append({'line': lineno, 'reason': str(error)})
    ordered = sorted(unique.values(), key=lambda x: (x['eventUTC'], x['row']['account'], x['row']['source'], x['row']['eventId']))
    # This deterministic display is not proof of a causal ordering.
    return {'events': ordered, 'duplicates': duplicates, 'rejected': rejected}


def definitely_before(left, right, left_error_seconds, right_error_seconds):
    if left_error_seconds < 0 or right_error_seconds < 0:
        raise ValueError('negative clock bound')
    return timestamp(left)+dt.timedelta(seconds=left_error_seconds) < timestamp(right)-dt.timedelta(seconds=right_error_seconds)


def event(account, source, eid, event_time, received, action, request='r-17'):
    return {'account': account, 'source': source, 'eventId': eid, 'requestId': request,
            'eventTime': event_time, 'receivedTime': received, 'action': action}


def encode(rows):
    return ('\n'.join(json.dumps(row, ensure_ascii=False, separators=(',', ':')) for row in rows)+'\n').encode()


def main(output=None):
    checks = []
    def check(name, condition):
        if not condition:
            raise AssertionError(name)
        checks.append({'id': name, 'pass': True})
    rows = [
        event('funds-A', 'identity', 'auth-1', '2026-10-07T11:00:00+01:00', '2026-10-07T10:04:00Z', 'credential-used'),
        event('funds-A', 'gateway', 'export-1', '2026-10-07T10:01:00Z', '2026-10-07T10:02:00Z', 'export-requested'),
        event('funds-A', 'application', 'response-1', '2026-10-07T10:01:02Z', '2026-10-07T10:01:10Z', 'response-accepted'),
        event('funds-A', 'gateway', 'export-1', '2026-10-07T10:01:00Z', '2026-10-07T10:05:00Z', 'export-requested'),
        event('funds-B', 'gateway', 'export-1', '2026-10-07T10:01:01Z', '2026-10-07T10:02:05Z', 'export-requested'),
        event('funds-A', 'agent', 'clock-1', '2026-10-07T10:06:00Z', '2026-10-07T10:02:00Z', 'agent-observation'),
    ]
    with tempfile.TemporaryDirectory(prefix='dr-incident-local-') as temp:
        root = Path(temp);original = root/'synthetic-events.jsonl';raw = encode(rows);original.write_bytes(raw)
        acquired = root/'acquired.jsonl';shutil.copyfile(original, acquired)
        manifest = {'sha256': digest(raw), 'sizeBytes': len(raw), 'source': 'generated synthetic fixture', 'collector': 'local exercise', 'acquiredAt': dt.datetime.now(dt.timezone.utc).isoformat()}
        check('acquired-bytes-match-original', acquired.read_bytes() == original.read_bytes())
        check('acquired-hash-matches-manifest', digest(acquired.read_bytes()) == manifest['sha256'])
        check('acquired-size-matches-manifest', acquired.stat().st_size == manifest['sizeBytes'])
        check('timezone-normalization-same-instant', timestamp(rows[0]['eventTime']) == timestamp('2026-10-07T10:00:00Z'))
        result = analyze(acquired.read_bytes())
        check('six-raw-records-preserved', len(acquired.read_bytes().splitlines()) == 6)
        check('five-distinct-events', len(result['events']) == 5)
        check('one-received-duplicate', len(result['duplicates']) == 1)
        check('valid-fixture-no-rejected-lines', not result['rejected'])
        check('same-event-id-other-account-retained', sum(e['row']['eventId']=='export-1' for e in result['events']) == 2)
        check('duplicate-receive-time-preserved', result['duplicates'][0]['receivedTime'] == '2026-10-07T10:05:00Z')
        check('event-time-starts-with-credential-use', result['events'][0]['row']['eventId'] == 'auth-1')
        arrival = sorted(result['events'], key=lambda e: e['receivedUTC'])
        check('arrival-order-starts-with-response', arrival[0]['row']['eventId'] == 'response-1')
        check('arrival-order-differs-from-event-time', [e['row']['eventId'] for e in arrival] != [e['row']['eventId'] for e in result['events']])
        clock = next(e for e in result['events'] if e['row']['eventId']=='clock-1')
        check('future-event-negative-delay', clock['delaySeconds'] == -240)
        check('future-event-clock-warning', clock['clockWarning'])
        naive = event('funds-A','gateway','bad-time','2026-10-07T10:00:00','2026-10-07T10:01:00Z','unknown')
        check('naive-timestamp-rejected', analyze(encode([naive]))['rejected'][0]['reason'] == 'timezone required')
        broken = analyze(raw+b'{broken json}\n')
        check('malformed-line-is-quarantined', len(broken['rejected']) == 1 and len(broken['events']) == 5)
        missing = dict(rows[0]);del missing['account']
        check('non-object-json-rejected', analyze(b'[]\n')['rejected'][0]['reason'] == 'object required')
        check('missing-account-rejected', analyze(encode([missing]))['rejected'][0]['reason'] == 'missing identity field')
        conflicting = dict(rows[1]);conflicting['action']='export-denied'
        conflict_result = analyze(encode(rows+[conflicting]))
        check('conflicting-identity-flagged', conflict_result['rejected'][0]['reason'] == 'conflicting event identity')
        check('conflict-not-counted-as-benign-duplicate', len(conflict_result['duplicates']) == 1)
        try:
            definitely_before('2026-10-07T10:00:00Z','2026-10-07T10:01:00Z',-1,2)
            rejected_negative = False
        except ValueError:
            rejected_negative = True
        check('negative-clock-bound-rejected', rejected_negative)
        check('narrow-clock-bounds-order-known', definitely_before('2026-10-07T10:00:00Z','2026-10-07T10:01:00Z',2,2))
        check('overlapping-clock-bounds-order-unknown', not definitely_before('2026-10-07T10:00:00Z','2026-10-07T10:01:00Z',90,90))
        check('touching-bounds-not-strictly-before', not definitely_before('2026-10-07T10:00:00Z','2026-10-07T10:01:00Z',30,30))
        a_only = [e for e in result['events'] if e['row']['account']=='funds-A' and e['row']['requestId']=='r-17']
        check('correlation-keeps-account-boundary', len(a_only) == 4 and all(e['row']['account']=='funds-A' for e in a_only))
        edited = raw.replace(b'export-requested', b'export-denied', 1)
        check('edited-bytes-fail-original-hash', digest(edited) != manifest['sha256'])
        forged_manifest = {'sha256': digest(edited)}
        check('rewriting-both-file-and-manifest-evades-untrusted-hash', digest(edited) == forged_manifest['sha256'])
        check('original-remains-unchanged-after-analysis', original.read_bytes() == raw and acquired.read_bytes() == raw)
        facts = {'rawRecords': 6, 'uniqueEvents': 5, 'duplicateRecords': 1, 'clockWarnings': 1,
                 'firstByEventTime': result['events'][0]['row']['eventId'], 'firstByReceiveTime': arrival[0]['row']['eventId'],
                 'futureEventDelaySeconds': clock['delaySeconds'], 'rawSHA256': manifest['sha256'], 'sizeBytes': len(raw)}
    check('owned-temporary-files-cleaned', not root.exists())
    report = {'executedAt': dt.datetime.now(dt.timezone.utc).isoformat(), 'runtime': sys.version.split()[0],
              'scriptSHA256': digest(Path(__file__).read_bytes()), 'passed': len(checks), 'failed': 0,
              'checks': checks, 'facts': facts, 'acquisitionManifest': manifest,
              'scope': 'Actual local file copy, SHA-256 checks and parsing of fictional JSONL incident events.',
              'limits': ['No actual compromise, cloud API, CloudTrail validation, SIEM or forensic imaging',
                         'Synthetic event timestamps and clock bounds, not measured clocks',
                         'A local mutable manifest is not an authenticated source or tamper-proof custody record',
                         'Display order and matching request IDs do not prove causality or human attribution',
                         'First conflicting event retained for inspection; rejected conflicts must be reviewed before conclusions',
                         'No legal admissibility or notification-deadline determination',
                         'No independent specialist review']}
    if output:Path(output).write_text(json.dumps(report,indent=2)+'\n')
    print(json.dumps({'passed': report['passed'], 'failed': 0, 'scriptSHA256': report['scriptSHA256'], 'facts': facts}))

if __name__ == '__main__':main(sys.argv[1] if len(sys.argv)>1 else None)
NA PRÁTICA

11:00+01:00 corresponde a 10:00Z; ordem de receção não prova ordem causal. Seis linhas conservam cinco eventos distintos.

Armadilhas comuns

Confundir hash com autenticidade; receção com ocorrência; IDs iguais entre contas com duplicados; endpoint recuperado com incidente encerrado.

Tópicos relacionados: Operação, risco e gestão de mudanças · Evidência, responsabilidade e aceitação

Leva esta ideia contigo

Constrói uma cronologia com limites explícitos e coordena contenção, evidência e recuperação.

Criar conta

Referência: Incident Response Recommendations and Considerations for Cybersecurity Risk Management · CCSP examination outline effective 2026-08-01; January2026 V2 PDF

CCSP® é uma marca registada de ISC2, Inc. A dr.pt é uma plataforma de preparação independente e não está afiliada, associada, patrocinada, autorizada nem aprovada por ISC2. Os conteúdos e as perguntas são originais, não são perguntas oficiais de exame, e concluir os nossos testes não atribui nem garante qualquer certificação. Os nomes são usados apenas para identificar o tema. Todas as outras marcas pertencem aos respetivos titulares.