diff --git a/Dockerfile b/Dockerfile index 9ce90e3..2ec942f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM crystallang/crystal RUN useradd -ms /bin/bash notroot COPY ./ /breakout WORKDIR /breakout +RUN shards install RUN chown -R notroot:notroot /breakout USER notroot RUN crystal build -Dpreview_mt --error-trace src/docker-escape.cr diff --git a/README.md b/README.md index 3fa0bcd..be3537f 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Additionally, if the tool will conduct a quick port scan of available interfaces Use a prebuilt binary from [Releases]("https://github.com/PercussiveElbow/docker-escape-tool/releases") or compile yourself with Crystal 0.31.1 or higher: ``` +shards install crystal build -Dpreview_mt src/docker-escape.cr ``` @@ -52,4 +53,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/shard.yml b/shard.yml index b11e45f..6c6e77b 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: docker-escape -version: 0.1.0 +version: 0.1.1 authors: - your-name-here @@ -8,6 +8,10 @@ targets: docker-escape: main: src/docker-escape.cr -crystal: 0.31.1 +dependencies: + net_sample: + github: arcage/net_sample.cr + +crystal: 0.35.1 license: MIT diff --git a/src/checks/network_socket/network_socket_check.cr b/src/checks/network_socket/network_socket_check.cr index 06cc3f6..613af7e 100644 --- a/src/checks/network_socket/network_socket_check.cr +++ b/src/checks/network_socket/network_socket_check.cr @@ -1,6 +1,6 @@ require "uri" require "http/client" -require "../../utils/network/net_sample" +#require "../../utils/network/net_sample" require "../../utils/network/port_scan" require "socket" diff --git a/src/docker-escape.cr b/src/docker-escape.cr index 901536e..4e40e8b 100644 --- a/src/docker-escape.cr +++ b/src/docker-escape.cr @@ -1,6 +1,7 @@ require "./utils/*" require "./checks/*" require "./breakouts/*" +require "net_sample" def main logo() diff --git a/src/utils/network/libc_net/nic.cr b/src/utils/network/libc_net/nic.cr deleted file mode 100644 index 8cfa3ef..0000000 --- a/src/utils/network/libc_net/nic.cr +++ /dev/null @@ -1,93 +0,0 @@ -require "./nic/*" - -lib LibC - IFHWADDRLEN = 6 - INET_ADDRSTRLEN = 16 - INET6_ADDRSTRLEN = 46 - - fun getifaddrs(ifaddr : Ifaddrs**) : Int - fun freeifaddrs(ifaddr : Ifaddrs*) : Void - fun inet_ntop(af : Int, src : Void*, dst : Char*, size : SocklenT) : Char* -end - -class NetSample::NIC - @@nics : Hash(String, self)? - - def self.nics - @@nics ||= get_nic_info - end - - def self.ifnames - self.nics.keys - end - - def self.inaddr_of(if_name) - self.nics[if_name]?.try &.inaddr - end - - def self.in6addr_of(if_name) - self.nics[if_name]?.try &.in6addr - end - - def self.hwaddr_of(if_name) - self.nics[if_name]?.try &.hwaddr - end - - def self.[](if_name) - self.nics[if_name] - end - - def self.[]?(if_name) - self.nics[if_name]? - end - - def self.each(&block) - self.nics.values.each do |nic| - yield nic - end - end - - property inaddr : String? - property in6addr : String? - setter hwaddr : Bytes? - - def initialize(@name : String) - end - - def to_s(io : IO) - io << "" - end - - private def internal_hwaddr(io : IO) - i = 0 - if bytes = @hwaddr - byte_size = bytes.size - loop do - io << ("%02x" % bytes[i]) - i += 1 - if i == byte_size - break - end - io << ':' - end - io << ":??" unless byte_size == LibC::IFHWADDRLEN - end - end - - def hwaddr - if bytes = @hwaddr - String.build do |io| - hwaddr(io) - end - else - nil - end - end -end diff --git a/src/utils/network/libc_net/nic/bsd.cr b/src/utils/network/libc_net/nic/bsd.cr deleted file mode 100644 index 31e5849..0000000 --- a/src/utils/network/libc_net/nic/bsd.cr +++ /dev/null @@ -1,73 +0,0 @@ -{% skip_file unless flag?(:bsd) || flag?(:darwin) %} - -lib LibC - AF_LINK = 18 - SDL_DATA_SIZE = 256 - - struct SockaddrDl - sdl_len : UChar - sdl_family : SaFamilyT - sdl_index : UShort - sdl_type : UChar - sdl_nlen : UChar - sdl_alen : UChar - sdl_slen : UChar - sdl_data : Char[SDL_DATA_SIZE] - {% if flag?(:darwin) %} - sdl_rcf : UShort - sdl_route : UShort[16] - {% end %} - end - - struct Ifaddrs - ifa_next : Ifaddrs* - ifa_name : Char* - ifa_flags : UInt - ifa_addr : Sockaddr* - ifa_netmask : Sockaddr* - ifa_dstaddr : Sockaddr* - ifa_data : Void* - end -end - -class NetSample::NIC - private def self.get_nic_info : Hash(String, self) - nics = Hash(String, self).new { |h, k| h[k] = self.new(k) } - if LibC.getifaddrs(out ifaddrs) == -1 - raise Errno.new("errno #{Errno.value} on getifaddr()") - end - ifap = ifaddrs.as(LibC::Ifaddrs*) - while ifap - ifa = ifap.value - if ifa_addr = ifa.ifa_addr - if_name = String.new(ifa.ifa_name) - case ifa_addr.value.sa_family - when LibC::AF_INET - ina = ifa_addr.as(LibC::SockaddrIn*).value - dst = StaticArray(UInt8, LibC::INET_ADDRSTRLEN).new(0) - addr = ina.sin_addr.s_addr - LibC.inet_ntop(LibC::AF_INET, pointerof(addr).as(Void*), dst, LibC::INET_ADDRSTRLEN) - nics[if_name].inaddr = String.new(dst.to_unsafe) - when LibC::AF_INET6 - ina = ifa_addr.as(LibC::SockaddrIn6*).value - dst = StaticArray(UInt8, LibC::INET6_ADDRSTRLEN).new(0) - addr6 = ina.sin6_addr.__u6_addr.__u6_addr8 - LibC.inet_ntop(LibC::AF_INET6, addr6.to_unsafe.as(Void*), dst, LibC::INET6_ADDRSTRLEN) - nics[if_name].in6addr = String.new(dst.to_unsafe) - when LibC::AF_LINK - dla = ifa_addr.as(LibC::SockaddrDl*).value - if (alen = dla.sdl_alen) == LibC::IFHWADDRLEN - nlen = dla.sdl_nlen - data = dla.sdl_data.to_slice.clone - alen = nlen > LibC::SDL_DATA_SIZE - LibC::IFHWADDRLEN ? LibC::SDL_DATA_SIZE - nlen : LibC::IFHWADDRLEN - hwaddr = data[nlen, alen] - nics[if_name].hwaddr = hwaddr - end - end - end - ifap = ifa.ifa_next - end - LibC.freeifaddrs(ifaddrs) - nics - end -end diff --git a/src/utils/network/libc_net/nic/linux.cr b/src/utils/network/libc_net/nic/linux.cr deleted file mode 100644 index eaabcd1..0000000 --- a/src/utils/network/libc_net/nic/linux.cr +++ /dev/null @@ -1,62 +0,0 @@ -{% skip_file unless flag?(:linux) %} - -lib LibC - AF_PACKET = 17 - - struct SockaddrLl - sll_family : UShort - sll_protocol : UInt16 - sll_ifinex : Int - sll_hatype : UShort - sll_pkttype : UChar - sll_halen : UChar - sll_addr : StaticArray(UChar, 8) - end - - union IfaIfu - ifu_broadaddr : Sockaddr* - ifu_dstaddr : Sockaddr* - end - - struct Ifaddrs - ifa_next : Ifaddrs* - ifa_name : Char* - ifa_flags : UInt - ifa_addr : Sockaddr* - ifa_netmask : Sockaddr* - ifa_ifu : IfaIfu - ifa_data : Void* - end -end - -class NetSample::NIC - private def self.get_nic_info : Hash(String, self) - nics = Hash(String, self).new { |h, k| h[k] = self.new(k) } - if LibC.getifaddrs(out ifaddrs) == -1 - raise Errno.new("errno #{Errno.value} on getifaddr()") - end - ifap = ifaddrs.as(LibC::Ifaddrs*) - while ifap - ifa = ifap.value - if ifa_addr = ifa.ifa_addr - if_name = String.new(ifa.ifa_name) - case ifa_addr.value.sa_family - when LibC::AF_INET - ina = ifa_addr.as(LibC::SockaddrIn*).value - dst = StaticArray(UInt8, LibC::INET_ADDRSTRLEN).new(0) - addr = ina.sin_addr.s_addr - LibC.inet_ntop(LibC::AF_INET, pointerof(addr).as(Void*), dst, LibC::INET_ADDRSTRLEN) - nics[if_name].inaddr = String.new(dst.to_unsafe) - when LibC::AF_PACKET - lla = ifa_addr.as(LibC::SockaddrLl*).value - data = lla.sll_addr.to_slice.clone - hwaddr = data[0, LibC::IFHWADDRLEN] - nics[if_name].hwaddr = hwaddr - end - end - ifap = ifa.ifa_next - end - LibC.freeifaddrs(ifaddrs) - nics - end -end diff --git a/src/utils/network/libc_net/ping.cr b/src/utils/network/libc_net/ping.cr deleted file mode 100644 index 6bbb76f..0000000 --- a/src/utils/network/libc_net/ping.cr +++ /dev/null @@ -1,35 +0,0 @@ -require "math" -require "./ping/icmp" -require "./ping/client" -require "./ping/result" - -# TODO: Write documentation for `Ping` -module NetSample::Ping - VERSION = "0.1.0" - - class Error < Exception; end - - def self.command(host : String, *, count : UInt16 = 5u16, data = "ping!") - raise Error.new("ping count must be greater than 0.") unless count > 0 - data_length = [56, data.size].max - puts "PING #{host}: #{data_length} data bytes" - - client = Ping::Client.new(host) - id = Process.pid.to_u16 - sequence = 0u16 - rtt_list = [] of Float64 - while sequence < count - res = client.ping(id, sequence, data) - puts res.message - rtt_list << res.rtt if res.is_a?(EchoReplyReceived) && res.valid? - sleep(1) - sequence += 1 - end - received = rtt_list.size - puts "\n--- #{host} ping statistics ---" - puts "#{count} packets transmitted, #{received} packets received, #{"%.1d" % (100.0 - (received.to_f * 100 / count))}% packet loss" - avg = rtt_list.reduce { |a, i| a + i } / received - stddev = Math.sqrt(rtt_list.map { |rtt| rtt - avg }.reduce { |a, i| a + (i ** 2) } / received) - puts "round-trip min/avg/max/stddev = #{"%.3f" % rtt_list.min}/#{"%.3f" % avg}/#{"%.3f" % rtt_list.max}/#{"%.3f" % stddev} ms" - end -end diff --git a/src/utils/network/libc_net/ping/client.cr b/src/utils/network/libc_net/ping/client.cr deleted file mode 100644 index dd21f79..0000000 --- a/src/utils/network/libc_net/ping/client.cr +++ /dev/null @@ -1,33 +0,0 @@ -module NetSample::Ping - class Client - class IllegalReplyReceived < Exception; end - - @socket : Socket - @host : Socket::IPAddress - - def initialize(host : String) - @host = Socket::IPAddress.new(host, 0) - @socket = Socket.new(Socket::Family::INET, - Socket::Type::RAW, - Socket::Protocol::ICMP) - @socket.read_timeout = 5.second - end - - def ping(id : UInt16, sequence : UInt16, message : String) - send_icmp = ICMP::EchoRequest.new(id, sequence, message) - bytes = send_icmp.to_bytes - begin - LibC.sendto(@socket.fd, bytes.to_unsafe.as(Void*), bytes.size, 0, @host, @host.size) - send_time = Time.now - bytes = Bytes.new(1500) - bytes_read, _ = @socket.receive(bytes) - rescue ex - return Result.load(send_icmp, ex) - end - receive_time = Time.now - rtt = (receive_time - send_time).total_milliseconds - packet = ICMP::Packet.new(bytes[0, bytes_read]) - return Result.load(send_icmp, packet, rtt) - end - end -end diff --git a/src/utils/network/libc_net/ping/icmp.cr b/src/utils/network/libc_net/ping/icmp.cr deleted file mode 100644 index 60c24bd..0000000 --- a/src/utils/network/libc_net/ping/icmp.cr +++ /dev/null @@ -1,144 +0,0 @@ -module NetSample::Ping - module ICMP - class ChecksumError < Exception; end - - def self.checksum(data : Bytes) - io = IO::Memory.new(data.size) - io.write(data) - io.rewind - sum = 0u32 - size = io.size - read_size = 0 - while size - read_size > 1 - sum += io.read_bytes(UInt16, IO::ByteFormat::NetworkEndian) - read_size += 2 - end - unless size == read_size - sum += io.read_byte.not_nil! - end - sum = (sum & 0xffff) + (sum >> 16) - sum = (sum & 0xffff) + (sum >> 16) - ~(sum.to_u16) - end - - def self.checksum!(data : Bytes) - raise ChecksumError.new unless checksum(data) == 0u16 - end - - class Packet - getter version : UInt8 - getter protocol : UInt8 - getter ttl : UInt8 - getter src : String - getter dst : String - getter data : Bytes - - def initialize(bytes : Bytes) - @version = (bytes[0] & 0xf0) >> 4 - raise "Non IPv4 packet received." unless version == 4 - ip_header_length = (bytes[0] & 0x0f) * 4 - ip_header = bytes[0, ip_header_length] - @protocol = ip_header[9] - raise "Non ICMP packet received." unless protocol == 1 - @ttl = ip_header[8] - @dst = ip_header[12, 4].join(".") - @src = ip_header[16, 4].join(".") - data_length = bytes.size - ip_header_length - @data = bytes[ip_header_length, data_length] - end - - def echo_reply? - data.first == 0u8 - end - - def echo_request? - data.first == 8u8 - end - end - - abstract class Data - getter type : UInt8 - getter code : UInt8 - getter checksum : UInt16 - - @type = 0u8 - @code = 0u8 - @checksum = 0u16 - - abstract def data(io) : Bytes - - def data - io = IO::Memory.new(1500) - data(io) - io.rewind - io.to_slice - end - - def to_bytes - io = IO::Memory.new(1500) - io.write_byte(@type) - io.write_byte(@code) - io.write_bytes(@checksum, IO::ByteFormat::NetworkEndian) - data(io) - io.rewind - io.to_slice - end - end - - abstract class EchoData < Data - getter id : UInt16 - getter sequence : UInt16 - getter message : String - - def initialize(@id, @sequence, @message) - @checksum = ICMP.checksum(to_bytes) - end - - def initialize(bytes : Bytes) - io = IO::Memory.new(1500) - io.write(bytes) - io.rewind - initialize(io) - end - - def initialize(io : IO) - @type = io.read_byte.not_nil! - @code = io.read_byte.not_nil! - @checksum = io.read_bytes(UInt16, IO::ByteFormat::NetworkEndian) - @id = io.read_bytes(UInt16, IO::ByteFormat::NetworkEndian) - @sequence = io.read_bytes(UInt16, IO::ByteFormat::NetworkEndian) - @message = io.gets_to_end - if @checksum == 0u16 - @checksum = ICMP.checksum(to_bytes) - else - ICMP.checksum!(to_bytes) - end - end - - def data(io : IO) - io.write_bytes(@id, IO::ByteFormat::NetworkEndian) - io.write_bytes(@sequence, IO::ByteFormat::NetworkEndian) - io.write(@message.to_slice) - while io.size < 64 - io.write_byte(0u8) - end - end - - def_equals type, code, checksum, sequence, message - end - - class EchoRequest < EchoData - @type : UInt8 = 8u8 - end - - class EchoReply < EchoData - def self.from_request(req : EchoRequest) - bytes = req.to_bytes - bytes[0] = 0u8 - bytes[2] = 0u8 - bytes[3] = 0u8 - self.new(bytes) - end - end - end -end diff --git a/src/utils/network/libc_net/ping/result.cr b/src/utils/network/libc_net/ping/result.cr deleted file mode 100644 index 872614a..0000000 --- a/src/utils/network/libc_net/ping/result.cr +++ /dev/null @@ -1,71 +0,0 @@ -module NetSample::Ping - abstract class Result - def self.load(echo_request : ICMP::EchoRequest, result : ICMP::Packet | Exception, rtt : Float64 = 0.0) - if result.is_a?(ICMP::Packet) - case result.data[1] - when 0u8 - return EchoReplyReceived.new(echo_request, result, rtt) - when 3u8 - return DestinationUnreachable.new(echo_request, result, rtt) - else - return OtherICMPRecieved.new(echo_request, result, rtt) - end - else - return ExceptionOccur.new(echo_request, result) - end - end - - @valid : Bool = false - - def valid? - @valid - end - - abstract def message : String - end - - abstract class ICMPReceived < Result - getter ip : ICMP::Packet - getter type : UInt8 - getter code : UInt8 - getter rtt : Float64 - - def initialize(@echo_request : ICMP::EchoRequest, @ip : ICMP::Packet, @rtt : Float64) - @type = @ip.data[0] - @code = @ip.data[1] - end - end - - class EchoReplyReceived < ICMPReceived - def message - echo_reply = ICMP::EchoReply.new(@ip.data) - @valid = (echo_reply == ICMP::EchoReply.from_request(@echo_request)) - if valid? - "#{@ip.data.size} bytes from #{@ip.dst}: icmp_seq=#{echo_reply.sequence} ttl=#{@ip.ttl} time=#{"%.3f" % @rtt} ms" - else - "Data mismatch reply received." - end - end - end - - class DestinationUnreachable < ICMPReceived - def message - "Destination Unreachable.(code: #{@code}" - end - end - - class OtherICMPRecieved < ICMPReceived - def message - "Other ICMP packet received.(type: #{@type}, code: #{@code})" - end - end - - class ExceptionOccur < Result - def initialize(@echo_request : ICMP::EchoRequest, @ex : Exception) - end - - def message - "#{@ex.message} (#{@ex.class})" - end - end -end diff --git a/src/utils/network/net_sample.cr b/src/utils/network/net_sample.cr deleted file mode 100644 index 79eed0b..0000000 --- a/src/utils/network/net_sample.cr +++ /dev/null @@ -1,7 +0,0 @@ -require "socket" - -module NetSample - VERSION = "0.2.2" -end - -require "./libc_net/*"