ASP.NET WebAPI로 Port Knocking을 만들어 보았다.

Port Knocking?



시스템 관리는 원격지의 서버에 관리자로서 액세스할 필요가 있지만 흔히 있다. ssh나 https로 암호화된 통로를 사용하지만, 외부에 상시 노출되어 있는 관리 포트는 brute-force 공격을 받는다.

특히 ssh의 경우는, 넷상에 tcp/22만 우울한 봇이 존재 정도의 리스크이지만, fail2ban나 sshguard와 같은 일정 회수 이상 실패했을 경우, 자동적으로 시스템의 방화벽(iptables, pf, firewalld... )를 이용하여 일정 시간 차단하는 방법이 필요하다.

항상 서비스가 필요하지 않은, 즉 관리 목적의 서비스에 적합한 방법이 "포트 - 노킹 (Port Knocking)"이다. 클라이언트는, 데몬의 사전 약속한 캐릭터 라인을 건네주어, 미리 약속된 포트를 조금의 사이만 열어 두는 식이다. 이렇게하면 포트가 항상 열릴 필요는 없습니다.

기본 API



제공하는 명령은 다음과 같습니다.
request:
POST -d '{action:"allow", ip:"127.0.0.1", port:8080}' /api/portkock
POST -d '{action:"deny", ip:"127.0.0.1", port:8080}' /api/portkock

response:
when OKay, {"result":"OK"}
when NG, {"result":"NG", "message":"bla bla ..."}

구현



참조에 "C:\Windows\System32\FirewallAPI.dll"을 추가해야 합니다. 다음과 같은 간단한 Controller를 만들어 보았다.

using PortKnockService;
using System;
using System.Runtime.Serialization;
using System.Web.Http;

namespace PortKockWebApi.Controllers
{
    public class PortKnockController : ApiController
    {

        [HttpGet]
        public PortKnockResponse DoGet(PortKnockRequest req)
        {
            return new PortKnockResponse()
            {
                Result = "NG",
                Message = "unknown method"
            };
        }


        [HttpPost]
        public PortKnockResponse DoPost(PortKnockRequest req)
        {

            // need to log here.

            if (req.Action.ToLower() == "allow") {
                try
                {
                    FirewallUtils.AllowAddressPort(req.Ip, req.Port);
                    return new PortKnockResponse()
                    {
                        Result = "OK",
                        Message = null
                    };

                }
                catch (Exception e)
                {
                    return new PortKnockResponse()
                    {
                        Result = "NG",
                        Message = e.Message
                    };
                }
            }
            else if (req.Action.ToLower() == "deny")
            {
                try
                {
                    FirewallUtils.CloseAddressPort(req.Ip, req.Port);
                    return new PortKnockResponse()
                    {
                        Result = "OK",
                        Message = null
                    };

                }
                catch (Exception e)
                {
                    return new PortKnockResponse()
                    {
                        Result = "NG",
                        Message = e.Message
                    };
                }
            }

            return new PortKnockResponse()
            {
                Result = "NG",
                Message = "unknown error"
            };
        }
    }

    [Serializable]
    [DataContract(Name = "")]
    public class PortKnockRequest
    {
        [DataMember(Name = "action", IsRequired = true)]
        public string Action { get; set; }

        [DataMember(Name = "ip", IsRequired = true)]
        public string Ip { get; set; }

        [DataMember(Name = "port", IsRequired = true)]
        public int Port { get; set; }
    }

    [Serializable]
    [DataContract(Name = "")]
    public class PortKnockResponse
    {
        [DataMember(Name = "result")]
        public string Result { get; set; }

        [DataMember(Name = "message", IsRequired =false)]
        public string Message { get; set; }

    }
}


구현한 것



동작 확인



실행할 때는 Firewall을 조작하기 때문에 관리자 권한이 필요하다.


curl에서 실행


Powershell에서 결과를 확인했습니다.


개선점



  • proper listing feature current opened destinations
    list opened destinations
    request:
        GET /api/portkock[?items=50&page=1]
    response:
        {items:[
            {id="", requested_at="", expired_at="", destination={action:"allow", ip:"127.0.0.1", port:8080}, result:{}},
            ...
        ]}
    

  • proper getting request specific item details feature
    request:
        GET /api/portkock/<id>
    response:
        ...
    
  • proper logging feature
  • proper persistent to records
  • proper scheduling to automated expiration
  • not to show IIS error page
  • 좋은 웹페이지 즐겨찾기