해당 시리즈는 방주원 저자의
네트워크 공격패킷 분석
책의 내용을 기반으로 합니다.
포트 스캐닝은 특정 호스트에서 열려있는 포트들을 확인하는 행위입니다. 포트 스캐닝을 통해 서버에서 구동중인 서비스들(ex. http/https, mysql, ssh 등..)을 확인할 수 있으며, 공격자들은 이를 이용해 취약점이 있을 법한 서비스를 파악하고 공격을 수행할 수 있습니다.
TCP Open
기법에서 핸드쉐이킹 마지막 과정인 ACK
를 보내지 않아, 세션 수립을 막고 로그에 찍히는 걸 방지하는 방식FIN(연결 종료)
플래그를 보내 반응 여부를 확인하는 기법RST-ACK
가 반환된다.주의사항: 허가받지 않은 서버를 상대로 하는 포트 스캐닝은 엄연한 침입행위입니다
이번 게시글에서는 비교적 구현이 쉬운 TCP Half Scan을 구현해 보도록 하겠습니다.
앞으로 제가 구현한 코드들은 전부 제 깃허브에서 확인하실 수 있습니다.
🔗 Github에서 전체 코드 확인하기
해당 게시글은 winpcap와 pcap4j가 설치되어 있다는 가정 하에 작성됩니다.
먼저 패킷을 수신하고 발신할 수 있는 핸들러를 생성해야 합니다.
val nif = NifSelector().selectNetworkInterface() // 네트워크 인터페이스 선택
val handle = nif.openLive(65536, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 1000) // 핸들러 생성
pcap4j에서는 사용자가 손쉽게 네트워크 인터페이스
를 선택할 수 있도록 위와 같은 선택창을 지원합니다. (사진에서 나온 인터페이스들은 다 내부망 주소라서 그냥 모자이크를 생략했습니다)
이후 클라이언트에게 SYN 패킷을 보내야 하기 때문에, 이를 위한 TCP 패킷을 작성해 주어야 합니다.
// SYN 요청을 위한 TCP 패킷 생성(전송 계층)
val tcpSynPacket = TcpPacket.Builder()
.syn(true) // flag를 SYN으로 설정
.sequenceNumber((Math.random() * 100000000).toInt()) // 시퀀스 넘버는 랜덤으로
.srcAddr(myIp).srcPort(TcpPort.getInstance((50000 + Math.random() * 9000).toInt().toShort())) // 공격자의 주소와 포트 설정
.dstAddr(targetIp).dstPort(TcpPort.getInstance(port)) // 타겟의 주소와 포트 설정
.correctChecksumAtBuild(true) // 체크섬 자동생성
.correctLengthAtBuild(true) // 길이 자동설정
.window(1024) // window size는 nmap과 동일하게 설정
.paddingAtBuild(true) // padding 자동 설정
.options(listOf( // 단편화 최대크기 설정(nmap과 동일하게 설정)
TcpMaximumSegmentSizeOption.Builder().maxSegSize(1460).length(4).correctLengthAtBuild(true).build()
))
실제 코드에는 TCP를 페이로드로 삼을 IPv4와 Ethernet(데이터링크 계층)의 패킷 생성도 필요하지만, 여기에 작성할 경우 가독성도 떨어질 뿐더러 이번 주제와는 관련성이 낮은 내용이기 때문에 제외하였습니다. (코드 전체는 앞서 말씀드린 것처럼 Github를 참고해 주세요)
앞선 과정으로 SYN 패킷을 전송하였다면, 이제 타겟이 반환하는 패킷의 결과를 분석하여야 합니다.
해당 코드 역시 중요한 부분만 작성하였습니다.
fun isOpen(packet: Packet, targetIp: Inet4Address, targetPort: Short): OpenState {
if(!packet.contains(TcpPacket::class.java)) return OpenState.NOT_THIS_PACKET // TCP 패킷이 아니면 리턴
val ipPacket = packet.get(IpV4Packet::class.java)
val tcpPacket = packet.get(TcpPacket::class.java)
if(ipPacket.header.srcAddr != targetIp) return OpenState.NOT_THIS_PACKET
if(tcpPacket.header.ack && tcpPacket.header.srcPort.value() == targetPort) { // ACK OOO이여야 여부 확인 가능
return if(tcpPacket.header.syn) { // SYN-ACK
OpenState.OPEN
}else if(tcpPacket.header.rst) { // RST-ACK
OpenState.CLOSED
}else { // 기타 경우인 경우
OpenState.NOT_THIS_PACKET
}
}else{
return OpenState.NOT_THIS_PACKET
}
}
초반에 코드를 작성한 뒤, Wireshark를 통한 테스트 및 결과를 확인해 본 결과 SYN 패킷은 정상적으로 발신되지만 SYN-ACK
가 수신되지 않는 것이 확인되었습니다. 이 말인 즉슨, 타겟이 공격자의 요청을 수신하지 못한다는 의미였습니다.
이 문제를 해결하기 위해 nmap과의 작동 비교는 물론, 몇 시간에 걸친 구글링까지 하게 되었습니다만, 사실 알고보니 조금 어이없는 것이 원인이었습니다.
바로 체크섬
문제였습니다. TCP 단에서의 체크섬에는 자동생성을 설정해 놓았지만, IPv4 단에서는 설정을 빼먹은 것(...) 때문에 작동하지 않은 것입니다.
포트 스캐닝을 막는 방식은 다른 공격들에 비해 비교적 간단하다고 말씀드릴 수 있습니다.
이러한 공격을 막기 위해서는, HTTP/HTTPS와 같이 서비스 포트를 제외한 내부용 포트(mysql 등)는 전부 방화벽에서 차단하는 것이 좋습니다. 일반적으로 대부분의 서비스들은 화이트리스트(whitelist)
를 통해 80(HTTP), 443(HTTPS)등의 포트만 외부 접근을 허용하고 있습니다.
어쩌면 간단하다고도 할 수 있는 첫번째 네트워크 공격의 구현부터 어려운 난관에 봉착할 뻔했는데요, 그래도 무사히 잘 완성해서 다행인 거 같습니다.
다음 시간에는 DDoS 공격에 사용되는 패킷
에 대해 알아보고, 직접 구현하며 방어법을 파악해보는 시간을 가져보도록 하겠습니다. 감사합니다.