바인딩()은 수행하지만 수신()은 수행하지 않는 응용 프로그램/포트를 어떻게 찾을 수 있습니까?

바인딩()은 수행하지만 수신()은 수행하지 않는 응용 프로그램/포트를 어떻게 찾을 수 있습니까?

결함이 있는 응용 프로그램이 bind()TCP 소켓을 사용하여 특정 포트를 호출했지만 P따르지 않는 경우 listen()해당 포트는 P열린 포트에 나열되지 않습니다. 즉, netstat표시 ss되지 ls /proc/net/tcp않지만 포트가 점유되어 다른 응용 프로그램에서 사용할 수 없습니다. 그러한 애플리케이션과 포트를 찾을 수 있는 합리적인 방법이 있습니까?

답변1

더 적절한 것이 나올 때까지 bind(2)TCP 소켓에서 사용되는 프로세스를 찾기 위해 확실히 비산업적인 방식으로 시도하지만 둘 다 listen(2)찾지 않고 connect(2)바인딩된 TCP 주소가 무엇인지 보여주는 답변이 있습니다.

필요getfattrattr대부분의 배포판에서 지정된 패키지에 포함되어 있습니다.커널 >= 3.7비TCP 소켓을 필터링하고 설치를 최소화합니다.gdb(예를 들어 Debian의 경우:gdb-minimal). 개발 환경이 필요하지 않습니다. 다음과 같이 실행해야합니다뿌리사용자(그렇지 않으면 동일한 사용자에 대한 정보만 찾을 수 있지만 컨테이너 전체에서는 작동하지 않습니다.) 마지막에 있는 참고 사항을 참조하세요.


요소:

  • 첫 번째 셸 스크립트는 기능의 일부를 모방 lsof하지만 이 특정 경우에만 해당됩니다. 모든 프로세스에서 소켓 FD를 검색합니다. 속성이 있는 소켓의 경우 TCP또는 TCPv6(파일 메타 속성으로 system.sockprotoname사용 가능)getfattr, 발견된 대로 lsof사용됩니다.getxattr(2)이 방법으로 최소한 TCP 소켓임을 보여줍니다.) 다음을 확인하십시오(양말 파일 시스템의사 파일 시스템) inode는 해당 네트워크 네임스페이스에서 찾을 수 있습니다 tcp.tcp6 프로세스파일에 없으면 pid, fd 및 inode가 후보 3개로 표시됩니다. 이 스크립트만으로도 "결함 있는" 프로세스를 찾아 나열할 수 있습니다.

    findbadtcpprocs.sh:

    #!/bin/sh
    
    find /proc -mindepth 1 -maxdepth 1 -name '[1-9]*' |
        xargs -I{} find {}/fd -follow -type s 2>/dev/null |
            while read procfd; do
                type=$(getfattr --absolute --only-values -L -n system.sockprotoname $procfd | tr '\0' '\n')
                if [ "$type" = "TCP" -o "$type" = "TCPv6" ]; then
                    inode=$(stat -L -c %i $procfd)
                    pid=$(echo $procfd | cut -d/ -f3)
                    if awk '$10 == inode { exit 1 }' inode=$inode /proc/$pid/net/tcp /proc/$pid/net/tcp6; then
                        fd=$(echo $procfd | cut -d/ -f5)
                        echo $pid $fd $inode
                    fi
                fi
            done 
    

    이 스크립트는 추가 정보 없이 후보 프로세스만 찾기 위해 독립형으로 사용할 수 있습니다.

  • gdb그런 다음 올바른 값을 제공해야 하는 스크립트 가 있습니다.FD정보. 이는 후보 프로세스에 연결되어 (먼저 일부 메모리를 할당하고) run 을 실행하고 getsockname(2), 바인딩된 소켓을 표시하고(할당된 리소스를 해제하고) 프로세스를 해제합니다.

    getsockname.gdb:

    set $malloc=(void *(*)(long long)) malloc
    set $ntohs=(unsigned short(*)(unsigned short)) ntohs
    p $malloc(64)
    p $malloc(4)
    set *(long *)$2=64
    p (int) getsockname($fd,$1,$2)
    set logging file /dev/stdout
    set logging on
    if *((short *) $1) == 2
        set $ip=(unsigned char *) ($1+4)
        printf "%hu.%hu.%hu.%hu",$ip[0],$ip[1],$ip[2],$ip[3]
    else
        if *((short *) $1) == 10
            set $ip6=(unsigned short *) ($1+8)
            printf "[%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx]",$ntohs($ip6[0]),$ntohs($ip6[1]),$ntohs($ip6[2]),$ntohs($ip6[3]),$ntohs($ip6[4]),$ntohs($ip6[5]),$ntohs($ip6[6]),$ntohs($ip6[7])
        end
    end
    printf ":%hu\n",$ntohs(*(unsigned short *)($1+2))
    set logging off
    call (void) free($2)
    call (void) free($1)
    quit
    
  • 마지막으로 글루 스크립트는 작업 편의성을 위해 이전 두 스크립트를 사용합니다. 동일한 소켓을 공유하는 여러 프로세스(또는 스레드)에 쓸데없이 연결되는 것을 방지합니다.

    result.sh:

    #!/bin/sh
    
    oldinode=-1
    ./findbadtcpprocs.sh | sort -s -n -k 3 | while read pid fd inode; do
        printf '%d\t%d\t%d\t' $pid $fd $inode
        if [ $inode -ne $oldinode ]; then
            socketname=$(gdb -batch-silent -p $pid -ex 'set $fd'=$fd -x ./getsockname.gdb 2>/dev/null) || socketname=FAIL
            oldinode=$inode
        fi
        printf '%s\n' "$socketname"
    done
    

    모든 것을 사용 가능하게 하려면 다음 명령을 실행하세요.

    chmod a+rx findbadtcpprocs.sh result.sh
    ./result.sh
    
  • 보너스로 C 소스 코드의 간단한 재생기는 동일한 TCP 소켓을 사용하지 않고도 동일한 TCP 소켓을 사용하여 두 개의 프로세스를 생성합니다 listen(2). 사용법: gcc -o badtcpbind badtcpbind.c./badtcpbind 5555

    badtcpbind.c:

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <strings.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        int sockfd;
        struct sockaddr_in myaddr;
        if (argc < 2) {
            exit(2);
        }
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("socket");
            exit(1);
        }
        bzero(&myaddr, sizeof myaddr);
        myaddr.sin_family = AF_INET;
        myaddr.sin_addr.s_addr = INADDR_ANY;
        myaddr.sin_port = htons(atoi(argv[1]));
        if (bind(sockfd, (struct sockaddr *) &myaddr, sizeof myaddr) < 0) {
            perror("bind");
            exit(1);
        }
    
    #if 0
         listen(sockfd,5);
    #endif
    
        fork();
        sleep(9999);
    }
    

예:

# ./badtcpbind 5555 &
[1] 330845
# ./result.sh 
108762  20  303507  0.0.0.0:0
330845  3   586443  0.0.0.0:5555
330846  3   586443  0.0.0.0:5555

(예, 알 수 없는 이유로 libvirtdTCP 소켓을 생성하는 프로세스가 여기에 표시되지만 소켓은 사용되지 않으며 결과의 첫 번째 줄에 캡처됩니다.)


지침:

  • 가독성과 효율성을 향상하려면 쉘보다 더 나은 언어를 사용해야 할 수도 있습니다.

  • 확실히 에 비해 더 활력이 넘칩니다 lsof.

  • 여기에서 수행된 방식으로 실행 중인 프로세스에 연결하는 데 문제가 있습니다.

    • 정적으로 링크된 바이너리에서는 작동하지 않습니다( malloc()함수나 특정 기호 정의를 사용할 수 없는 경우).
    • 디버깅 정보를 사용할 수 없으므로 대부분의 함수에는 명시적인 범위가 있으며 변경하지 않으면 모든 환경에서 실행되지 않을 수 있습니다(AMD64커널 5.10.x 아키텍처, Debian Bullseye, Debian 10 및 CentOS 7 사용자 공간).
    • 또한, 일반적인 glibc가 아닌 다른 libcs와 링크된 바이너리는 그대로 작동하지 않을 수 있습니다.
    • 방해가 되며 취약한(특히 다중 스레드) 응용 프로그램이 중단될 수 있습니다. 확인이 완료되지 않았습니다(예: malloc(3)또는 getsockname(2)실패).
  • 고려해야 할 마지막 스크립트양말 파일 시스템inode는 네트워크 네임스페이스별로가 아니라 전역적으로 고유합니다. 이를 증명하려고 시도하지는 않았지만 스크립트가 더 단순해졌습니다.

관련 정보