Jira 이슈 생성 연동

이번 글에서는 Atlassian Jira 연동 예제를 통해 로그프레소에서 REST API를 어떻게 호출하는지 알아봅니다.

Atlassian Jira API 레퍼런스는 Jira에 이슈를 생성하려면 POST 엔드포인트와 본문 데이터를 어떻게 전송해야 하는지 설명하고 있습니다. curl 예제에서 필수적인 부분만 정리하면 아래와 같습니다.

curl --request POST \
  --url 'https://your-domain.atlassian.net/rest/api/3/issue' \
  --user 'email@example.com:<api_token>' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{
  "fields": {
    "project": "PROJECT_KEY",
    "summary": "이슈 제목",
    "issuetype": {
      "id": "10002"
    },
    "description": {
      "version": 1
      "type": "doc",
      "content": [
        {
          "type": "paragraph",
          "content": [
            {
              "type": "text",
              "text": "이슈 설명"
            }
          ]
        }
      ],
    }
  }
}'

로그프레소에서는 dict() 함수를 이용하여 맵 (map) 타입의 값을 표현할 수 있습니다. dict() 함수는 키, 값 쌍을 순차적으로 입력받습니다. 예를 들어, 아래와 같은 JSON 객체를 표현하려면,

{"project": "PROJECT_KEY", "summary": "이슈 제목"}

로그프레소 쿼리에서는 아래와 같이 표현할 수 있습니다.

dict("project", "PROJECT_KEY", "summary", "이슈 제목")

리스트 (list) 타입의 값을 표현하려면 array() 함수를 사용합니다. 예를 들어, 다음과 같은 JSON 배열을 표현하려면,

[{"type": "text", "text": "이슈 설명"}]

로그프레소 쿼리에서는 아래와 같이 표현할 수 있습니다.

array(dict("type", "text", "text", "이슈 설명"))

가변적으로 값을 생성하려면 입력 레코드의 description 필드를 활용할 수 있습니다.

array(dict("type", "text", "text", description))

즉, dict()와 array()를 중첩하면 Jira에서 요구하는 복잡한 형태의 본문 데이터를 정의할 수 있습니다. 아래의 로그프레소 쿼리에서 PROJECT_KEY, YOUR_ID, EMAIL_ADDR, API_TOKEN 값을 변경하여 실행해보세요.

API_TOKEN 발급 방법은 Atlassian Jira 앱 설치 가이드를 참고하시기 바랍니다.

json "{}" 
| eval project_key = "PROJECT_KEY", issue_type = "10002", summary = "이슈 제목", description = "이슈 설명"
| eval headers = dict("Accept", "application/json", "Content-Type", "application/json", "User-Agent", "Logpresso") 
| eval fields = 
    dict("project", dict("key", project_key), 
         "summary", summary, 
         "issuetype", dict("id", issue_type),
         "description", dict("version", 1,
                             "type", "doc",
                             "content", array(dict("type", "paragraph", 
                                                   "content", array(dict("type", "text", "text", description))))))
| eval url = "https://YOUR_ID.atlassian.net/rest/api/3/issue"
| wget method=post auth="EMAIL_ADDR:API_TOKEN" header=headers format=json

여기에서 사용된 wget 명령어의 옵션 설명은 아래와 같습니다:

  • method: POST 메소드 지정
  • auth: HTTP Basic 인증에 필요한 계정 및 암호 지정
  • header: Jira REST API 호출에 필요한 HTTP 헤더 키/값 쌍을 포함한 맵 필드를 지정
  • format: json 포맷을 지정하면 입력 레코드로 전달되는 모든 필드를 하나의 JSON 객체로 직렬화하여 HTTP 본문으로 전송

로그프레소 3은 wget 쿼리를 복잡하게 만들어서 실행해야 하지만, 로그프레소 4에서는 Jira 앱을 설치하여 편리하게 원하시는 작업을 수행할 수 있습니다. 이제 SIEM을 Jira에 통합하여 원하시는 업무 흐름을 만들어보세요.

둘러보기

더보기

액티브 디렉터리 LDAP 연동

액티브 디렉터리는 중앙 집중적으로 인증을 처리하며 그룹 정책을 관리하기 때문에 계정, 호스트 등 많은 양의 정보가 내재되어 있습니다. 온프레미스 환경에서 액티브 디렉터리의 정보를 통합하려면 LDAP 프로토콜을 사용해야 합니다. 로그프레소에서 액티브 디렉터리를 연동하려면 먼저 LDAP 프로파일을 등록해야 합니다. ![](/media/ko/2023-05-23-ldapsearch/ldap_profile.png) 설정이 완료되면 아래와 같이 `ldapsearch` 쿼리와 LDAP 필터를 이용하여 액티브 디렉터리에 등록된 계정 목록을 조회할 수 있습니다: ``` ldapsearch profile=AD filter="(&(userPrincipalName=*))" ``` ![LDAP 계정 목록 조회](/media/ko/2023-05-23-ldapsearch/ldap_users.png) 수십 가지의 계정 정보를 확인할 수 있습니다만, 타임스탬프는 [윈도우 FileTime 형식](https://learn.microsoft.com/ko-kr/windows/win32/api/minwinbase/ns-minwinbase-filetime)이기 때문에 보기가 어렵습니다. FileTime은 아래와 같이 변환하여 볼 수 있습니다. ``` ldapsearch profile=AD filter="(&(userPrincipalName=*))" | eval lastLogon = if(lastLogon == "0", null, epoch(floor(long(lastLogon) / 10000 - 11644473600000))) | eval badPasswordTime = if(badPasswordTime == "0", null, epoch(floor(long(badPasswordTime) / 10000 - 11644473600000))) | order sAMAccountName, lastLogon, badPasswordTime ``` ![LDAP FileTime 변환](/media/ko/2023-05-23-ldapsearch/ldap_users_filetime.png) 액티브 디렉터리에 조인된 호스트 목록은 아래와 같이 확인할 수 있습니다. ``` ldapsearch profile=AD filter="(&(servicePrincipalName=*))" | eval lastLogon = epoch(floor(long(lastLogon) / 10000 - 11644473600000)) | order cn, operatingSystem, operatingSystemVersion, lastLogon, whenCreated, whenChanged ``` ![LDAP 호스트 목록](/media/ko/2023-05-23-ldapsearch/ldap_hosts.png) 이렇게 로그프레소에 액티브 디렉터리 데이터를 통합하면 더 이상 사용되지 않는 계정이나 오래된 시스템을 주기적으로 식별하고 정리할 수 있으므로, 잠재적인 공격 표면을 줄이고 안정적으로 내부 IT 인프라를 관리할 수 있습니다.

2023-05-23

NTFS 포렌식

NTFS는 윈도우즈 운영체제에서 지금까지 사용하고 있는 파일시스템입니다. 파일시스템은 물리적인 디스크 공간을 논리적인 디렉터리 계층과 파일 단위로 구조화하여 사용할 수 있도록 만들어줍니다. 스마트폰이 대중화된 이후 포렌식의 분석 대상이 개인정보를 집적하고 있는 모바일 기기로 많이 이동하고 있지만 여전히 윈도우즈 운영체제는 업무에서 핵심적인 생산성 도구입니다. 악성코드에 감염되거나 정보가 유출되었을 때 호스트에서 어떤 일이 발생했는지 알아내려면 NTFS 파일시스템에 대한 이해가 필수적입니다. ## MFT: 마스터 파일 테이블 ![](/media/ko/2020-09-12-ntfs-forensic/mft-structure.png) 마스터 파일 테이블은 NTFS 파일시스템에 존재하는 모든 디렉터리와 파일에 대한 메타데이터를 유지합니다. 마스터 파일 테이블은 1024바이트 고정 길이 레코드로 구성되어 있으며, 파일 삭제 여부, 파일 크기, 디스크 할당 크기 등을 48바이트의 MFT 헤더에 담고 있습니다. 파일 이름, 접근 권한, 파일 생성/수정/접근 일시 등의 정보는 MFT 헤더 뒤에 이어지는 여러 가지의 속성 (Attribute) 에 포함됩니다. 분석에 가장 흔히 사용되는 속성은 $STANDARD_INFORMATION 과 $FILE_NAME 속성이며, 아래의 필드를 포함하고 있습니다. * 파일 이름 * 디스크 할당 크기 * 실제 크기 * 파일 생성 일시 * 파일 수정 일시 * MFT 레코드 변경 일시 * 파일 액세스 일시 * 파일 권한 * 파일 속성 (숨김, 압축 등) 파일을 삭제하더라도 마스터 파일 테이블에서 즉시 삭제되지는 않으므로, 악성코드나 중요한 증적 파일이 삭제되더라도 MFT에서 삭제 흔적을 확인할 수 있습니다. 로그프레소 포렌식의 ntfs-mft 커맨드는 아래와 같은 필드 출력을 제공합니다. * no: MFT 인덱스 번호 * file_path: 파일 경로 * file_name: 파일 이름 * file_size: 파일 크기 * alloc_size: 디스크 할당 크기 * in_use: 삭제 여부 * is_dir: 디렉터리 여부 * link_count: 하드링크 수 * created_at: 파일 생성일시 ($FILENAME 속성) * modified_at: 파일 변경일시 ($FILENAME 속성) * access_at: 파일 접근일시 ($FILENAME 속성) * mft_modified_at: MFT 레코드 변경일시 ($FILENAME 속성) * std_created_at: 파일 생성일시 ($STANDARD_INFORMATION 속성) * std_modified_at: 파일 수정일시 ($STANDARD_INFORMATION 속성) * std_mft_modified_at: MFT 레코드 변경일시 ($STANDARD_INFORMATION 속성) * std_access_at: 파일 접근일시 ($STANDARD_INFORMATION 속성) * is_readonly: 읽기전용 여부 * is_hidden: 숨김 여부 * is_system: 시스템 파일 여부 * is_archive: 보관 가능 여부 * is_device: 장치 여부 * is_normal: 일반 여부 * is_temp: 임시 파일 여부 * is_sparse: Sparse 여부 * is_reparse: Reparse 여부 * is_compressed: 압축 여부 * is_offline: 오프라인 여부 * is_indexed: 인덱스 여부 * is_encrypted: 암호화 여부 * lsn: $LogFile 시퀀스 번호 * seq: MFT 레코드 시퀀스 (레코드 재할당 시 증가) * file_ref: 파일 참조 번호 * parent_file_ref: 디렉터리 파일 참조 번호 * parent_no: 디렉터리 MFT 인덱스 번호 ## USNJRNL: 저널링 로그 MFT에서 파일 삭제를 확인할 수는 있지만 사건을 조사할 때 또 하나 중요한 부분은 삭제 시점입니다. NTFS 파일시스템은 저널링을 지원하기 때문에, 파일 변경 이력을 $Extend 폴더의 $UsnJrnl 파일에 기록합니다. ![](/media/ko/2020-09-12-ntfs-forensic/usnjrnl-structure.png) 따라서 USNJRNL 파일을 분석하면 시스템에서 언제, 어떤 파일을 대상으로 어떤 작업을 수행했는지 파악할 수 있습니다. ntfs-usnjrnl 커맨드는 아래와 같은 필드 출력을 제공합니다. * _time: 파일 이벤트 시각 * file_name: 파일 이름 * file_no: MFT 인덱스 번호 * file_ref: 파일 참조 번호 * parent_file_no: 디렉터리 MFT 인덱스 번호 * parent_file_ref: 디렉터리 참조 번호 * reason: 파일 작업 목록 * usn: 레코드 오프셋 (Update Sequence Number) 저널링 로그는 MFT를 번호로 참조하기 때문에, 아래와 같이 조인하여 완전한 파일 경로를 확보할 수 있습니다. ```query ntfs-usnjrnl USNJRNL | join file_no [ ntfs-mft MFT | rename no as file_no ] ``` ![](/media/ko/2020-09-12-ntfs-forensic/ntfs-usnjrnl-join.png) 이러한 파일 변경 이력은 간단히 통계 처리하여 아래와 같이 타임라인을 시각화 할 수도 있습니다. ```query ntfs-usnjrnl USNJRNL | explode reason | timechart span=1h count by reason ``` ![](/media/ko/2020-09-12-ntfs-forensic/ntfs-usnjrnl-timechart.png) ## 코드게이트 포렌식 문제 연습 로그프레소 포렌식 솔루션은 쿼리를 기반으로 다양한 포렌식 아티팩트를 연관 분석하는 강력한 기능을 지원합니다. 아래에서는 이전 코드게이트 2012 컨퍼런스에서 NTFS와 관련하여 출제된 문제를 어떻게 분석하는지 설명합니다. > In Energy corporate X which is located in Seoul, APT(Advanced Persistent Threat) was occurred. For 6 months, Attacker A has stolen critical information with an elaborate attack. > Attacker A exerted great effort to remove his all traces such as malicious file, prefetch, registry and event logs for the period of attacking, so it was hard for Energy Corporate X to find an attacking path. > However IU who is Forensic expert can find the traces of the malicious files Attacker A used by analyzing MFT (Master File Table). > What time malicious file was created? The time is based on Korea Standard Time (UTC +09:00) * Codegate 2012 Forensic 400 문제 파일 400점 문제이지만 풀이 방법을 알면 간단하게 접근할 수 있습니다. 문제 지문에서는 MFT를 분석 대상으로 제시하고 있고, 공격자가 프리페치를 삭제하려고 시도하였다고 언급하여 실행 파일을 암시하고 있습니다. 문제의 목표는 악성코드 생성일시를 찾는 것입니다. ```query ntfs-mft E:\codegate2012\$MFT | search file_name == "*.exe" ``` ![](/media/ko/2020-09-12-ntfs-forensic/codegate-query.png) 간단한 확장자 검색으로 휴지통에 있는 r32.exe 파일이 2012-02-23 02:39:18 KST에 생성되었고 파일 크기가 82944 바이트라는 사실을 확인할 수 있습니다.

2020-09-12

레지스트리 포렌식

레지스트리는 윈도우즈 운영체제와 응용프로그램에 관련된 방대한 설정과 운영 정보가 기록된 데이터베이스입니다. 윈도우즈 운영체제 초창기에는 INI 파일을 사용했으나, 레지스트리가 도입되면서 표준화된 계층적 데이터 구조, 다중 사용자 환경 지원, 접근 권한 제어, 바이너리 포맷 파일 기반의 효율적 I/O, 타입 시스템, 트랜잭션 등을 제공하게 되었습니다. 레지스트리를 분석하면 어떤 프로그램이나 서비스가 부팅 시 자동으로 실행되는지, 어떤 프로그램을 최근에 실행했는지, 어떤 프로그램을 얼마나 오래 사용했는지, 최근 어떤 파일을 검색했는지, 어떤 파일을 열어봤는지, 어느 서버에 접속했는지, 어떤 파일을 압축했는지 등 무수히 많은 정보를 추출할 수 있습니다. 따라서 레지스트리 분석은 사고 조사 초기에 수행해야 할 중요한 단계이며, 여기에서 확인된 정보에 따라 후속 조사의 진행이 결정될 수 있습니다. ## 레지스트리 하이브 파일 레지스트리 편집기(regedit)를 통해 하나로 보이는 레지스트리의 계층적 구조는 물리적으로 여러 개의 레지스트리 하이브 파일에 분산되어 있습니다. ![](/media/ko/2020-11-01-registry-forensic/reg-hive-files.png) `%SystemRoot%\System32\config` 디렉터리에는 아래와 같은 레지스트리 하이브 파일이 존재합니다. * SAM: HKEY_LOCAL_MACHINE\SAM * SECURITY: HKEY_LOCAL_MACHINE\Security * SOFTWARE: HKEY_LOCAL_MACHINE\Software * SYSTEM: HKEY_LOCAL_MACHINE\System 또한 각 사용자 계정의 디렉터리에는 NTUSER.DAT 레지스트리 하이브 파일이 존재합니다. ## HIVE 파일 구조 레지스트리 하이브 파일은 아래와 같이 구성되어 있습니다. ![](/media/ko/2020-11-01-registry-forensic/hive-structure.png) BASE 블록 구조 ![](/media/ko/2020-11-01-registry-forensic/hive-base-block.png) HIVE BIN 헤더 구조 ![](/media/ko/2020-11-01-registry-forensic/hive-bin-header.png) 로그프레소 포렌식의 hive-file 커맨드는 아래와 같은 필드를 출력합니다. ![](/media/ko/2020-11-01-registry-forensic/hive-file-command.png) * key: 키 * type: 타입 (문자열, 이진값, DWORD, QWORD 등) * name: 값 * value: 데이터 * last_written: 마지막 기록 시각 ## 코드게이트 포렌식 문제 연습 로그프레소 포렌식 솔루션은 쿼리를 기반으로 다양한 포렌식 아티팩트를 연관 분석하는 강력한 기능을 지원합니다. 아래에서는 이전 코드게이트 2011 컨퍼런스에서 레지스트리와 관련하여 출제된 문제를 어떻게 분석하는지 설명합니다. > we are investigating the military secret's leaking. we found traffic with leaking secrets while monitoring the network. Security team was sent to investigate, immediately. But, there was no one present. > It was found by forensics team that all the leaked secrets were completely deleted by wiping tool. And the team has found a leaked trace using potable device. Before long, the suspect was detained. But he denies allegations. > Now, the investigation is focused on potable device. The given files are acquired registry files from system. The estimated time of the incident is Mon, 21 February 2011 15:24:28(KST). > Find a trace of portable device used for the incident. > The Key : "Vendor name" + "volume name" + "serial number" (please write in capitals) * Codegate 2011 Forensic 300 문제 파일 제시된 파일의 압축을 풀면 6개의 레지스트리 하이브 파일을 확인할 수 있습니다. 먼저 시스템에 마운트된 장치 정보를 추출하기 위해 SYSTEM 하이브 파일에서 MountedDevices 키를 검색하면 아래와 같이 이진값으로 된 레지스트리 데이터를 확인할 수 있습니다. ```query hive-file codegate2011\system.bak | search key == "*MountedDevices" and name == "\\DosDevices*" ``` ![](/media/ko/2020-11-01-registry-forensic/codegate-step1.png) 이 데이터를 UTF-16으로 디코드하면 아래와 같은 문자열을 확인할 수 있습니다. ```query hive-file codegate2011\system.bak | search key == "*MountedDevices" and name == "\\DosDevices*" | eval value = substr(decode(value, "UTF-16LE"), 4) ``` ![](/media/ko/2020-11-01-registry-forensic/codegate-step2.png) USB 값만 필터링해서 정규식으로 파싱하면 제조사, 모델명, 버전, 시리얼을 추출할 수 있습니다. ```query hive-file codegate2011\system.bak | search key == "*MountedDevices" and name == "\\DosDevices*" | eval value = substr(decode(value, "UTF-16LE"), 4) | search value == "*USB*" | rex field=value "Ven_(?<vendor>[^&]+)&Prod_(?<product>[^&]+)&Rev_(?<version>[^#]+)#(?<serial>[^&]+)" | eval serial = lower(serial) | fields vendor, product, version, serial, value ``` ![](/media/ko/2020-11-01-registry-forensic/codegate-step3.png) 그러나 아직 볼륨 이름과 장치를 연결한 시간을 확인하지 못한 상태입니다. 장치를 연결한 시간은 HKLM\SYSTEM\ControlSet00X\Enum\USB\VID_####&PID_#### 키의 마지막 수정 시간을 확인하면 됩니다. 아래와 같이 쿼리하면 66개의 키를 확인할 수 있습니다. ```query hive-file codegate2011\system.bak | search key == "*USB\\VID_*" | eval serial = lower(valueof(split(key, "\\"), 5)) | stats max(last_written) as last_connect by serial ``` ![](/media/ko/2020-11-01-registry-forensic/codegate-step4.png) 볼륨 이름은 HKLM\SOFTWARE\Microsoft\Windows Portable Devices\Devices 키에서 확인할 수 있습니다. 아래와 같이 쿼리하면 40개의 키를 확인할 수 있습니다. ```query hive-file codegate2011\software.bak | search key == "*Windows Portable Devices*" and name == "FriendlyName" | rex field=key "&REV_[^#]+#(?<serial>[^&]+)" | eval serial = lower(serial) | stats first(value) as volume_name by serial ``` ![](/media/ko/2020-11-01-registry-forensic/codegate-step5.png) 이 3종의 쿼리 결과를 시리얼 번호로 조인하면 원하는 결과를 한 번에 추출할 수 있습니다. ```query hive-file codegate2011\system.bak | search key == "*MountedDevices*" | eval value = substr(decode(value, "UTF-16LE"), 4) | search value == "*USB*" | rex field=value "Ven_(?<vendor>[^&]+)&Prod_(?<product>[^&]+)&Rev_(?<version>[^#]+)#(?<serial>[^&]+)" | eval serial = lower(serial) | stats count by vendor, product, version, serial, value | join serial [ hive-file codegate2011\system.bak | search key == "*USB\\VID_*" | eval serial = lower(valueof(split(key, "\\"), 5)) | stats max(last_written) as last_connect by serial ] | join serial [ hive-file codegate2011\software.bak | search key == "*Windows Portable Devices*" and name == "FriendlyName" | rex field=key "&REV_[^#]+#(?<serial>[^&]+)" | eval serial = lower(serial) | stats first(value) as volume_name by serial ] | search last_connect >= date("2011-02-21", "yyyy-MM-dd") and last_connect <= date("2011-02-22", "yyyy-MM-dd") | order volume_name, vendor, product, version, serial, last_connect ``` ![](/media/ko/2020-11-01-registry-forensic/codegate-step6.png) 이처럼 로그프레소 쿼리를 이용하여 레지스트리 포렌식 데이터를 손쉽게 분석하고 가공할 수 있으며, 재사용 가능한 라이브러리로 구축할 수 있습니다.

2020-11-01