원격데스크톱(RDP)에 대한 무차별 패스워드 대입 공격 차단하기

원격데스크톱(RDP)에 대한 무차별 패스워드 대입 공격 차단하기


관리하고 있는 서버 중 윈도우서버가 있는데 얼마전부터 원격데스크톱(RDP)에 대한 무작위 패스워드 대입 공격이 들어오기 시작했다.
문제는 이 공격이 지속되면서 서버 리소스를 과도하게 차지하기 시작했고, 이로 인해 서비스 차질이 발생했다는 것이다.
지금은 조치를 취해서 괜찮아졌지만 몇일동안 고생을 했다.
그럼 이 RDP 공격이란 무엇일까?

RDP 공격이란?

RDP 공격이란 인터넷에 노출된 RDP 서비스에 대해 패스워드를 무차별적으로 시도하거나 취약점을 이용하여 접속하는 공격이다.
일단 접속에 성공하면, 공격자는 해당 컴퓨터의 제어권을 얻고 다른 컴퓨터나 네트워크로 침투할 수 있다.
RDP 공격은 샘샘(SamSam), 크립토월(CryptoWall), 리브오프(Revil) 등 여러 랜섬웨어 그룹에 의해 사용된 바 있다. (이때 약 7,000대의 윈도우 PC와 1,900대의 서버가 감염됐다.)

RDP 공격을 차단하는 방법

RDP 공격을 차단하기 위해서는 다음과 같은 방법을 적용할 수 있다.
  • 패스워드를 복잡하고 강력하게 설정
  • RDP의 기본 포트인 3389번을 다른 포트로 변경 (방화벽에서 변경한 RDP 포트를 인바운드 규칙으로 허용)
  • 파워쉘 스크립트를 이용하여 보안 이벤트 로그에서 공격 IP를 추출하고 차단 (이번 포스팅에서 진행할 방법)
이처럼 RDP 공격을 차단하기 위해서는 여러 가지 방법이 있지만, 이번 포스팅에서는 파워쉘 스크립트를 이용하여 간단하게 구현해보도록 한다.
파워쉘은 윈도우에서 사용할 수 있는 스크립팅 언어와 셸 환경으로, 시스템 관리나 자동화 작업에 유용하다. 보통 기본설치 되어 있기 때문에 따로 프로그램을 설치하거나 세팅 할 필요가 없어서 윈도우 서버에서 자주 사용한다.
파워쉘 스크립트는 .ps1 확장자로 저장하고 실행할 수 있으며, 관리자 권한이 필요할 수 있다.

파워쉘 스크립트로 RDP 공격차단 스크립트 만들기

1. 로그 파일 생성

우선 RDP 접속 시도에 대한 로그 파일을 생성하기 위해 다음과 같은 코드를 작성한다.

1
2
3
4
5
6
7
8
9
10
# 로그 파일 경로 지정
$logPath = "C:\RdpLog.txt"
 
# 로그 파일이 없으면 생성
if (!(Test-Path $logPath)) {
    New-Item -ItemType File -Path $logPath | Out-Null
}
 
# 로그 파일에 날짜와 시간 추가
Add-Content -Path $logPath -Value "[$(Get-Date)]"
cs

위 코드는 C 드라이브에 RdpLog.txt라는 이름의 로그 파일을 생성하고 현재 날짜와 시간을 추가한다.
아래는 파워쉘 명령어에 대한 설명이다.

명령어

설명

Test-Path

파일이나 폴더의 존재 여부를 확인하는 명령어

New-Item

파일이나 폴더를 생성하는 명령어

Out-Null

출력 결과를 버리는 명령어 (리눅스의 cat >> /dev/null과 동일한 기능인 듯?)

Add-Content

파일에 내용을 추가하는 명령어

Get-Date

현재 날짜와 시간을 가져오는 명령어


2. 보안 이벤트 필터링

다음으로 보안 이벤트 로그에서 RDP 접속 시도에 대한 이벤트를 필터링하기 위해 다음과 같은 코드를 작성한다.

1
2
3
4
5
6
7
8
# 보안 이벤트 로그 가져오기
$securityLog = Get-WinEvent -LogName Security
 
# RDP 접속 시도에 해당하는 이벤트 ID 목록
$rdpEventIds = 4625 # 로그온 실패만 필터
 
# 보안 이벤트 로그에서 RDP 접속 시도만 필터링
Write-Host "Filtering RDP events from security log..."
$rdpEvents = $securityLog | Where-Object {$rdpEventIds -contains $_.Id}
cs

위 코드는 Get-WinEvent 명령어를 이용하여 보안 이벤트 로그를 가져오고, Where-Object 명령어를 이용하여 RDP 접속 시도에 해당하는 이벤트 ID만 필터링한다.
RDP 접속 시도에 해당하는 이벤트 ID는 다음과 같다.

1
2
3
1149: 사용자 인증 성공. 원격에서 사용자가 RDP 연결을 시도하고 연결에 성공하여 로그인 창이 떴을 때 발생합니다.
4624: 계정 로그온 성공. 원격에서 사용자가 RDP 연결을 통해 계정으로 로그온할 때 발생합니다.
4625: 계정 로그온 실패. 원격에서 사용자가 RDP 연결을 통해 계정으로 로그온할 때 실패할 때 발생합니다.
cs

이중 우리는 계정 로그온을 실패한 IP만 차단할 것이기 때문에 4625번만 필터링한다.

3. 공격 IP 차단

마지막으로 필터링된 RDP 접속 시도 이벤트에서 공격 IP를 추출하고 차단하기 위해 다음과 같은 코드를 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 공격 IP 목록 초기화
$attackIps = @()
 
# 로그온 실패 횟수 임계값 설정
$threshold = 10 # 원하는 값으로 변경 가능
 
# 필터링된 RDP 접속 시도 이벤트 반복문 실행 (프로그레스바 추가)
Write-Host "Extracting attack IPs from RDP events..."
$progress = 0 # 프로그레스바 초기값
$total = $rdpEvents.Count # 프로그레스바 최대값 (RDP 이벤트 개수)
foreach ($event in $rdpEvents) {
    # XML 형식의 메시지 데이터 가져오기
    $xmlData = [xml]$event.ToXml()
    # 메시지 데이터에서 원본 네트워크 주소 값 가져오기
    $sourceIp = $xmlData.Event.EventData.Data | Where-Object {$_.Name -eq "Source Network Address"| Select-Object -ExpandProperty "#text"
    # 원본 네트워크 주소 값이 유효한 IP인 경우 (로컬호스트 제외)
    if ($sourceIp -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" -and $sourceIp -ne "127.0.0.1") {
        # 메시지 데이터에서 로그온 실패 횟수 값 가져오기
        $failureCount = $xmlData.Event.EventData.Data | Where-Object {$_.Name -eq "FailureCount"| Select-Object -ExpandProperty "#text"
        # 로그온 실패 횟수가 임계값 이상인 경우에만 공격 IP 목록에 추가 (중복 제거)
        if ($failureCount -ge $threshold) {
            if ($attackIps -notcontains $sourceIp) {
                $attackIps += $sourceIp
            }
            # 로그 파일에 공격 IP와 관련된 정보 추가
            Add-Content -Path $logPath -Value "$($event.TimeCreated): $($event.Id) from $($sourceIp)"
        }
    }
    # 프로그레스바 업데이트
    Write-Progress `
        -Activity "Extracting attack IPs from RDP events" `
        -Status "$progress out of $total" `
        -PercentComplete (($progress / $total) * 100)
    # 프로그레스바 값 증가    
    $progress++
}
 
# 공격 IP 목록 반복문 실행 (프로그레스바 추가)
Write-Host "Creating firewall rules to block attack IPs..."
$progress = 0 # 프로그레스바 초기값
$total = $attackIps.Count # 프로그레스바 최대값 (공격 IP 개수)
foreach ($ip in $attackIps) {
    # 방화벽 규칙 이름 생성 (RdpBlock + IP)
    $ruleName = "RdpBlock" + $ip.Replace(".""_")
    # 방화벽 규칙이 존재하지 않으면 생성 (인바운드 TCP 포트 3389 차단)
    if (!(Get-NetFirewallRule -DisplayName $ruleName)) {
        New-NetFirewallRule `
            -DisplayName $ruleName `
            -Description "Block RDP from $ip" `
            -Direction Inbound `
            -Action Block `
            -Protocol TCP `
            -LocalPort 3389 `
            -RemoteAddress $ip | Out-Null
    }
    # 프로그레스바 업데이트
    Write-Progress `
        -Activity "Creating firewall rules to block attack IPs" `
        -Status "$progress out of $total" `
        -PercentComplete (($progress / $total) * 100)
    # 프로그레스바 값 증가    
    $progress++
}
cs

위 코드는 Get-NetFirewallRule과 New-NetFirewallRule 명령어를 이용하여 방화벽 규칙을 확인하고 생성한다.
방화벽 규칙의 이름은 RdpBlock + IP 형식으로 하고, 인바운드 TCP 포트 3389를 차단하는 방화벽 정책이 생성된다.
이렇게 하면 공격 IP로부터의 RDP 접속 시도를 막을 수 있다.

4. 전체 스크립트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# 로그 파일 경로 지정
$logPath = "C:\RdpLog.txt"
 
# 로그 파일이 없으면 생성
if (!(Test-Path $logPath)) {
    New-Item -ItemType File -Path $logPath | Out-Null
}
 
# 로그 파일에 날짜와 시간 추가
Add-Content -Path $logPath -Value "[$(Get-Date)]"
 
# 보안 이벤트 로그 가져오기
$securityLog = Get-WinEvent -LogName Security
 
# RDP 접속 시도에 해당하는 이벤트 ID 목록
$rdpEventIds = 4625 # 로그온 실패만 필터링
 
# 보안 이벤트 로그에서 RDP 접속 시도만 필터링
Write-Host "Filtering RDP events from security log..."
$rdpEvents = $securityLog | Where-Object {$rdpEventIds -contains $_.Id}
 
# 공격 IP 목록 초기화
$attackIps = @()
 
# 로그온 실패 횟수 임계값 설정
$threshold = 10 # 원하는 값으로 변경 가능
 
# 필터링된 RDP 접속 시도 이벤트 반복문 실행 (프로그레스바 추가)
Write-Host "Extracting attack IPs from RDP events..."
$progress = 0 # 프로그레스바 초기값
$total = $rdpEvents.Count # 프로그레스바 최대값 (RDP 이벤트 개수)
foreach ($event in $rdpEvents) {
    # XML 형식의 메시지 데이터 가져오기
    $xmlData = [xml]$event.ToXml()
    # 메시지 데이터에서 원본 네트워크 주소 값 가져오기
    $sourceIp = $xmlData.Event.EventData.Data | Where-Object {$_.Name -eq "Source Network Address"| Select-Object -ExpandProperty "#text"
    # 원본 네트워크 주소 값이 유효한 IP인 경우 (로컬호스트 제외)
    if ($sourceIp -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" -and $sourceIp -ne "127.0.0.1") {
        # 메시지 데이터에서 로그온 실패 횟수 값 가져오기
        $failureCount = $xmlData.Event.EventData.Data | Where-Object {$_.Name -eq "FailureCount"| Select-Object -ExpandProperty "#text"
        # 로그온 실패 횟수가 임계값 이상인 경우에만 공격 IP 목록에 추가 (중복 제거)
        if ($failureCount -ge $threshold) {
            if ($attackIps -notcontains $sourceIp) {
                $attackIps += $sourceIp
            }
            # 로그 파일에 공격 IP와 관련된 정보 추가
            Add-Content -Path $logPath -Value "$($event.TimeCreated): $($event.Id) from $($sourceIp)"
        }
    }
    # 프로그레스바 업데이트
    Write-Progress `
        -Activity "Extracting attack IPs from RDP events" `
        -Status "$progress out of $total" `
        -PercentComplete (($progress / $total) * 100)
    # 프로그레스바 값 증가    
    $progress++
}
 
# 공격 IP 목록 반복문 실행 (프로그레스바 추가)
Write-Host "Creating firewall rules to block attack IPs..."
$progress = 0 # 프로그레스바 초기값
$total = $attackIps.Count # 프로그레스바 최대값 (공격 IP 개수)
foreach ($ip in $attackIps) {
    # 방화벽 규칙 이름 생성 (RdpBlock + IP)
    $ruleName = "RdpBlock" + $ip.Replace(".""_")
    # 방화벽 규칙이 존재하지 않으면 생성 (인바운드 TCP 포트 3389 차단)
    if (!(Get-NetFirewallRule -DisplayName $ruleName)) {
        New-NetFirewallRule `
            -DisplayName $ruleName `
            -Description "Block RDP from $ip" `
            -Direction Inbound `
            -Action Block `
            -Protocol TCP `
            -LocalPort 3389 `
            -RemoteAddress $ip | Out-Null
    }
    # 프로그레스바 업데이트
    Write-Progress `
        -Activity "Creating firewall rules to block attack IPs" `
        -Status "$progress out of $total" `
        -PercentComplete (($progress / $total) * 100)
    # 프로그레스바 값 증가    
    $progress++
}
 
# 스크립트 완료 메시지 출력
Write-Host "Script completed. Check the log file and the firewall rules."
cs

5. 스크립트 실행방법

  • 스크립트를 텍스트 파일로 저장하고 확장자를 .ps1로 변경한다. 예: RdpBlock.ps1
  • 파워쉘을 관리자 권한으로 실행한다.
  • 스크립트가 있는 폴더로 이동한다. 예: cd C:\Scripts
  • 스크립트를 실행한다. 예: .\RdpBlock.ps1
  • 스크립트가 완료될 때까지 기다린다. 프로그레스바와 설명을 통해 진행 상황을 확인할 수 있다.
  • 스크립트가 완료되면 로그 파일과 방화벽 규칙을 확인한다.
진행상황을 프로그레스바를 통해 알 수 있다.
진행상황을 프로그레스바를 통해 알 수 있다.

작업완료
작업완료

마무리

RDP 공격은 지속적으로 발생하고 있으므로 주기적으로 보안 상태를 점검하고 업데이트하는 것이 중요하다.
또한 다른 보안 솔루션들도 함께 사용하여 보다 안전한 원격 환경을 구축할 수 있다.
아래 링크들은 RDP 공격에 대한 자세한 분석과 대응방법을 제공하는 자료이니 참고가 될 것이다.
이번 포스팅에서는 원격데스크톱(RDP)에 대한 무작위 패스워드 대입 공격을 차단하기 위한 파워쉘 스크립트를 작성해보았다.
이 스크립트는 이미 발생한 공격에 대해 후속조치를 하는 것으로, 실시간 방어기능은 없다.
다음에는 실시간으로 공격을 감지하고 차단하는 스크립트를 만들어보겠다.

글 쓰는 Jiniwar
글 쓰는 Jiniwar


댓글

이 블로그의 인기 게시물

crontab 설정방법과 로그 확인하는 법

Microsoft Defender 방화벽 설정 또는 해제하는 방법

한국 군비지출 세계 9위