1. 들어가기에 앞서

일반적으로 SSRF 공격을 막기 위해 개발자들은 다음과 같은 로직을 구현합니다.

  • 차단할 IP 범위
  • 도메인 이름 확인 (이상한 문자열로 우회하는 공격..)
  • DNS를 이용한 IP 확인

위 과정을 통과하게 되면 사용자가 입력한 URL로 접속하게 됩니다.

이러한 상황을 간단한 코드로 나타내면 다음과 같습니다.

const dns = require("dns/promises");
const axios = require("axios");

async function request(url){
    const check_domain_name = is_domain_name(url);
    const check_dns = await dns.resolve4(url);
    const check_ip = is_blacklist_ip(check_dns);
    
    if(check_ip || check_domain_name || check_dns){
    	return false;
    }

    const response = await axios.get(url);
    
    // ...
    // ...
}

 

하지만, `DNS를 이용한 ip 확인` 과 `axios.get()` 함수를 이용한 요청간에 코드상 문제점이 있습니다.

DNS 조회 결과 외부 IP로 확인 되었지만, get 요청 할 때는 내부 IP로 바껴 버리는 DNS Rebinding 공격에 취약하다는 점입니다. 

 

2. DNS Rebinding 공격 

위 코드의 문제점은 DNS 확인 시점 이후에, get 요청 시에 다시 요청되는 DNS 정보가 다른 IP로 바껴 버린다는 DNS Rebinding 공격에 취약하다는 점입니다. 

 

이를 실습하기 위한 nodejs 코드는 다음과 같습니다.

const express = require("express");
const dns = require("dns/promises");
const axios = require("axios");
const app = express();

dns.setServers([
        '1.1.1.1', '8.8.8.8'
])

app.get("/ssrf", async (req, res) => {
        const url = req.query.url;

        const result = await dns.resolve4(url);
        console.log("==== dns ====");
        console.log(result);

        const response = await axios.get("http://" + url);
        console.log("=== response ===");
        console.log(response.data);
        res.send("1");
})

app.listen(8000, () => {
        console.log("start");
})

 

위 코드를 실행하고 `http://localhost/ssrf?url=example.com` 으로 요청을 보내면 다음과 같은 결과를 얻을 수 있습니다.

아래 사진을 보면, dns 조회를 통한 ip 정보와 get 요청에 대한 응답 값을 볼 수 있습니다.

 

DNS Rebinding 공격을 하기 위해 환경을 구축할 필요 없이 아래 링크에서 간단하게 사용할 수 있습니다.

https://lock.cmpxchg8b.com/rebinder.html

 

아래 사진처럼 DNS에서 조회를 통해 우회할 IP와 get 요청을 통해 접속할 IP를 적어줍니다.

그럼 밑에 domain이 자동으로 생성 됩니다. 이를 위에서 만든 환경에 전달할 경우 어떻게 될까요?

 

DNS 조회 결과 123.123.123.123 라는 IP가 출력되었습니다. get 요청 시 123.123.123.123 은 80 포트가 열려있지 않은 상태 입니다. 하지만, get 요청에 대한 응답이 아래 사진 처럼 출력이 된 것을 볼 수 있습니다. 

즉, DNS Rebinding 공격으로 인해 서로 다른 IP가 각각 전달되어 동작한 것을 알 수 있습니다.

 

3. 끝마치며

위 상황에 대해서는 어떻게 대처해야 할지,, 코드를 어떤 식으로 작성해야 하는지는 모르겠네요.

처음에 생각했을 때는 DNS 조회 결과의 IP로 GET 요청을 보내면 되지 않을까 라는 생각을 했지만, 일부 사이트에서는 IP로 접근하는 요청을 차단하는 사이트가 있기 때문에,, 이 방법은 별로네요.

 

요청을 담당하는 서비스 혹은 봇만 따로 빼서 내부 서비스가 없는 환경에 별도로 동작 시키면 안전하지 않을까 생각이 듭니다.

 

코드 상으로 DNS Rebinding 공격을 어떻게 막는지 궁금하네요 ㅎㅎ

 

아, 해당 공격을 찾다가 다음과 같은 DNS Rebinding 공격 툴을 추가로 발견했습니다.

아직 써보진 않았지만, 한번 써봐야 겠네요.

https://github.com/makuga01/dnsFookup