403Webshell
Server IP : 119.195.102.159  /  Your IP : 216.73.217.134
Web Server : nginx/1.18.0
System : Linux picell 5.15.0-181-generic #191-Ubuntu SMP Fri May 22 19:09:02 UTC 2026 x86_64
User : altablue ( 1000)
PHP Version : 8.3.31
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/altablue/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/altablue/certbot-http01-diagnose.sh
#!/usr/bin/env bash
set -Eeuo pipefail

# ============================================================
# Let's Encrypt / Certbot HTTP-01 자동발급 통합 진단 스크립트
#
# 사용법:
#   sudo bash certbot-http01-diagnose.sh DOMAIN WEBROOT
#
# 예:
#   sudo bash certbot-http01-diagnose.sh \
#     hyh.picell.biz \
#     /home2/haeyulhome
#
# Certbot staging 실제 검증까지 실행:
#   sudo RUN_CERTBOT=1 bash certbot-http01-diagnose.sh \
#     hyh.picell.biz \
#     /home2/haeyulhome
#
# tcpdump 패킷 캡처까지 실행:
#   sudo RUN_CERTBOT=1 CAPTURE_TCP=1 bash certbot-http01-diagnose.sh \
#     hyh.picell.biz \
#     /home2/haeyulhome
#
# 주의:
# - RUN_CERTBOT=1은 실제 Let’s Encrypt staging 검증을 실행합니다.
# - nginx는 중단하지 않습니다. webroot 방식으로 검사합니다.
# - 테스트 challenge 파일은 종료 시 자동 삭제합니다.
# ============================================================

DOMAIN="${1:-}"
WEBROOT="${2:-}"

RUN_CERTBOT="${RUN_CERTBOT:-0}"
CAPTURE_TCP="${CAPTURE_TCP:-0}"
CERTBOT_EMAIL="${CERTBOT_EMAIL:-}"
REPORT_DIR="${REPORT_DIR:-/root/certbot-diagnose}"
TIMESTAMP="$(date '+%Y%m%d-%H%M%S')"
REPORT_FILE="${REPORT_DIR}/${DOMAIN:-unknown}-${TIMESTAMP}.log"
PCAP_FILE="${REPORT_DIR}/${DOMAIN:-unknown}-${TIMESTAMP}.pcap"

TEST_TOKEN="certbot-diagnose-${TIMESTAMP}-$$"
CHALLENGE_REL=".well-known/acme-challenge"
CHALLENGE_DIR=""
TEST_FILE=""

RED=$'\033[31m'
GREEN=$'\033[32m'
YELLOW=$'\033[33m'
CYAN=$'\033[36m'
RESET=$'\033[0m'

usage() {
    cat <<'EOF'
사용법:
  sudo bash certbot-http01-diagnose.sh DOMAIN WEBROOT

예:
  sudo bash certbot-http01-diagnose.sh hyh.picell.biz /home2/haeyulhome

환경변수:
  RUN_CERTBOT=1    Certbot staging HTTP-01 실제 검증 실행
  CAPTURE_TCP=1    Certbot 실행 중 TCP 80 패킷 캡처
  CERTBOT_EMAIL=   Certbot 계정 이메일. 비어 있으면 임시 등록 옵션 사용
  REPORT_DIR=      보고서 저장 디렉터리
EOF
}

if [[ -z "$DOMAIN" || -z "$WEBROOT" ]]; then
    usage
    exit 2
fi

if [[ "${EUID}" -ne 0 ]]; then
    echo "이 스크립트는 root 권한으로 실행해야 합니다."
    echo "sudo bash $0 $DOMAIN $WEBROOT"
    exit 1
fi

mkdir -p "$REPORT_DIR"
touch "$REPORT_FILE"
exec > >(tee -a "$REPORT_FILE") 2>&1

cleanup() {
    if [[ -n "${TEST_FILE:-}" && -f "$TEST_FILE" ]]; then
        rm -f "$TEST_FILE" || true
    fi
}
trap cleanup EXIT

section() {
    echo
    echo "${CYAN}============================================================${RESET}"
    echo "${CYAN}$*${RESET}"
    echo "${CYAN}============================================================${RESET}"
}

ok() {
    echo "${GREEN}[정상]${RESET} $*"
}

warn() {
    echo "${YELLOW}[주의]${RESET} $*"
}

fail() {
    echo "${RED}[오류]${RESET} $*"
}

run() {
    echo
    echo "+ $*"
    "$@" || true
}

command_exists() {
    command -v "$1" >/dev/null 2>&1
}

get_primary_interface() {
    ip route get 1.1.1.1 2>/dev/null |
        awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}'
}

get_public_ipv4() {
    local ip=""
    if command_exists curl; then
        ip="$(curl -4fsS --max-time 10 https://api.ipify.org 2>/dev/null || true)"
    fi
    printf '%s' "$ip"
}

get_dns_a() {
    if command_exists dig; then
        dig +short A "$DOMAIN" | sed '/^$/d'
    elif command_exists getent; then
        getent ahostsv4 "$DOMAIN" | awk '{print $1}' | sort -u
    fi
}

get_dns_aaaa() {
    if command_exists dig; then
        dig +short AAAA "$DOMAIN" | sed '/^$/d'
    fi
}

HTTP_CODE_LOCAL=""
HTTP_CODE_DOMAIN=""
HTTP_BODY_LOCAL=""
HTTP_BODY_DOMAIN=""

section "1. 기본 정보"

echo "실행 시각:       $(date -Is)"
echo "호스트명:        $(hostname -f 2>/dev/null || hostname)"
echo "도메인:          $DOMAIN"
echo "웹루트:          $WEBROOT"
echo "보고서:          $REPORT_FILE"
echo "Certbot 실행:    $RUN_CERTBOT"
echo "패킷 캡처:       $CAPTURE_TCP"

if [[ -d "$WEBROOT" ]]; then
    ok "웹루트 디렉터리가 존재합니다."
else
    fail "웹루트 디렉터리가 없습니다: $WEBROOT"
    exit 1
fi

section "2. 시스템 및 네트워크"

run uname -a
run ip -4 -br addr
run ip rule show
run ip route show table all

PRIMARY_IFACE="$(get_primary_interface || true)"
PUBLIC_IPV4="$(get_public_ipv4 || true)"
DNS_A="$(get_dns_a || true)"
DNS_AAAA="$(get_dns_aaaa || true)"

echo
echo "주 인터페이스:   ${PRIMARY_IFACE:-확인 실패}"
echo "공인 IPv4:       ${PUBLIC_IPV4:-확인 실패}"
echo "DNS A:"
printf '%s\n' "${DNS_A:-없음}"
echo "DNS AAAA:"
printf '%s\n' "${DNS_AAAA:-없음}"

if [[ -n "$PUBLIC_IPV4" && -n "$DNS_A" ]]; then
    if grep -Fxq "$PUBLIC_IPV4" <<<"$DNS_A"; then
        ok "DNS A 레코드가 현재 공인 IPv4와 일치합니다."
    else
        fail "DNS A 레코드가 현재 공인 IPv4와 일치하지 않습니다."
    fi
fi

if [[ -n "$DNS_AAAA" ]]; then
    warn "AAAA 레코드가 존재합니다. IPv6 nginx 리스닝과 IPv6 방화벽도 반드시 정상이어야 합니다."
fi

section "3. rp_filter 및 커널 네트워크 설정"

for key in \
    net.ipv4.conf.all.rp_filter \
    net.ipv4.conf.default.rp_filter \
    "net.ipv4.conf.${PRIMARY_IFACE:-all}.rp_filter" \
    net.ipv4.ip_forward; do
    sysctl "$key" 2>/dev/null || true
done

section "4. TCP 80/443 리스닝 상태"

run ss -lntp
echo
ss -lntp 2>/dev/null | grep -E '(:80[[:space:]]|:443[[:space:]])' || true

if ss -lnt 2>/dev/null | grep -qE '(:80[[:space:]])'; then
    ok "TCP 80 포트가 리스닝 중입니다."
else
    fail "TCP 80 포트가 리스닝 중이 아닙니다."
fi

section "5. nginx 상태와 설정"

if command_exists nginx; then
    run nginx -v
    run nginx -t

    if systemctl is-active --quiet nginx; then
        ok "nginx 서비스가 실행 중입니다."
    else
        fail "nginx 서비스가 실행 중이 아닙니다."
    fi

    echo
    echo "--- 도메인 관련 nginx -T 발췌 ---"
    nginx -T 2>&1 |
        awk -v domain="$DOMAIN" '
            BEGIN {show=0; count=0}
            $0 ~ domain {show=1; count=0}
            show {
                print
                count++
                if (count >= 80) show=0
            }
        ' || true

    echo
    echo "--- ACME location 전체 검색 ---"
    nginx -T 2>&1 |
        grep -nE 'server_name|root |alias |\.well-known/acme-challenge|listen 80|listen \[::\]:80' |
        tail -n 300 || true
else
    fail "nginx 명령이 없습니다."
fi

section "6. 웹루트 권한과 파일 구조"

run namei -l "$WEBROOT"
run ls -ld "$WEBROOT"
run find "$WEBROOT" -maxdepth 2 -type f \
    \( -name index.php -o -name index.html -o -name wp-config.php \) \
    -print

CHALLENGE_DIR="${WEBROOT}/${CHALLENGE_REL}"
TEST_FILE="${CHALLENGE_DIR}/${TEST_TOKEN}"

mkdir -p "$CHALLENGE_DIR"
printf '%s\n' "$TEST_TOKEN" > "$TEST_FILE"
chmod 755 "$WEBROOT" 2>/dev/null || true
chmod 755 "${WEBROOT}/.well-known" "$CHALLENGE_DIR" 2>/dev/null || true
chmod 644 "$TEST_FILE"

echo
echo "테스트 파일: $TEST_FILE"
run namei -l "$TEST_FILE"

section "7. ACME challenge 로컬 Host 헤더 테스트"

LOCAL_TMP="$(mktemp)"
DOMAIN_TMP="$(mktemp)"
trap 'cleanup; rm -f "$LOCAL_TMP" "$DOMAIN_TMP"' EXIT

HTTP_CODE_LOCAL="$(
    curl -sS \
        --max-time 15 \
        -o "$LOCAL_TMP" \
        -w '%{http_code}' \
        -H "Host: $DOMAIN" \
        "http://127.0.0.1/${CHALLENGE_REL}/${TEST_TOKEN}" \
        2>/dev/null || true
)"
HTTP_BODY_LOCAL="$(cat "$LOCAL_TMP" 2>/dev/null || true)"

echo "로컬 HTTP 코드: $HTTP_CODE_LOCAL"
echo "로컬 응답 본문: $HTTP_BODY_LOCAL"

if [[ "$HTTP_CODE_LOCAL" == "200" && "$HTTP_BODY_LOCAL" == "$TEST_TOKEN" ]]; then
    ok "nginx가 Host 헤더 기준 ACME 파일을 정상 제공합니다."
else
    fail "로컬 Host 헤더 ACME 테스트가 실패했습니다."
fi

section "8. 도메인 경유 HTTP 테스트"

HTTP_CODE_DOMAIN="$(
    curl -4sS \
        --max-time 20 \
        --connect-timeout 10 \
        -o "$DOMAIN_TMP" \
        -w '%{http_code}' \
        "http://${DOMAIN}/${CHALLENGE_REL}/${TEST_TOKEN}" \
        2>/dev/null || true
)"
HTTP_BODY_DOMAIN="$(cat "$DOMAIN_TMP" 2>/dev/null || true)"

echo "도메인 HTTP 코드: $HTTP_CODE_DOMAIN"
echo "도메인 응답 본문: $HTTP_BODY_DOMAIN"

if [[ "$HTTP_CODE_DOMAIN" == "200" && "$HTTP_BODY_DOMAIN" == "$TEST_TOKEN" ]]; then
    ok "서버 자체에서 도메인 경유 ACME 파일 조회가 정상입니다."
else
    warn "서버 자체의 도메인 경유 조회가 실패했습니다. NAT loopback 또는 외부 경로 문제일 수 있습니다."
fi

echo
echo "--- 상세 curl ---"
curl -4v \
    --max-time 20 \
    "http://${DOMAIN}/${CHALLENGE_REL}/${TEST_TOKEN}" \
    -o /dev/null || true

section "9. DNS 외부 리졸버 확인"

if command_exists dig; then
    for resolver in 1.1.1.1 8.8.8.8 9.9.9.9; do
        echo
        echo "--- Resolver ${resolver} ---"
        dig @"$resolver" +time=3 +tries=1 A "$DOMAIN" +short || true
        dig @"$resolver" +time=3 +tries=1 AAAA "$DOMAIN" +short || true
    done
else
    warn "dig 명령이 없어 외부 리졸버 확인을 생략합니다."
fi

section "10. UFW / iptables / nftables"

if command_exists ufw; then
    run ufw status verbose
    run ufw status numbered
else
    warn "ufw 명령이 없습니다."
fi

if command_exists iptables; then
    echo
    echo "--- iptables INPUT ---"
    iptables -L INPUT -n -v --line-numbers || true

    echo
    echo "--- UFW 사용자 입력 체인 ---"
    iptables -L ufw-user-input -n -v --line-numbers 2>/dev/null || true

    echo
    echo "--- DROP/REJECT/ipset/geo 관련 규칙 ---"
    iptables -S |
        grep -Ei 'DROP|REJECT|ipset|geo|country|dport 80|dports 80' || true
fi

if command_exists nft; then
    echo
    echo "--- nftables 관련 규칙 ---"
    nft -a list ruleset 2>/dev/null |
        grep -Ei 'drop|reject|dport (80|443)|ip saddr|set ' |
        head -n 500 || true
fi

section "11. Fail2ban 상태"

if command_exists fail2ban-client; then
    run fail2ban-client status
    echo
    fail2ban-client status 2>/dev/null |
        sed -n 's/.*Jail list:[[:space:]]*//p' |
        tr ',' '\n' |
        sed 's/^[[:space:]]*//;s/[[:space:]]*$//' |
        while read -r jail; do
            [[ -n "$jail" ]] || continue
            echo "--- Jail: $jail ---"
            fail2ban-client status "$jail" || true
        done
else
    warn "fail2ban-client 명령이 없습니다."
fi

section "12. 최근 커널 차단 로그"

journalctl -k --since "30 minutes ago" 2>/dev/null |
    grep -Ei 'DROP|REJECT|BLOCK|martian|reverse path|DPT=80|DPT=443' |
    tail -n 300 || true

section "13. Certbot 설치 및 인증서 상태"

if command_exists certbot; then
    run certbot --version
    run certbot plugins
    run certbot certificates
else
    fail "certbot 명령이 없습니다."
fi

section "14. 도메인 nginx 로그의 ACME 요청"

ACCESS_LOG="/var/log/nginx/${DOMAIN}.access.log"
ERROR_LOG="/var/log/nginx/${DOMAIN}.error.log"

if [[ -f "$ACCESS_LOG" ]]; then
    echo "--- $ACCESS_LOG ---"
    grep '/.well-known/acme-challenge/' "$ACCESS_LOG" |
        tail -n 100 || true
else
    warn "도메인별 access log가 없습니다: $ACCESS_LOG"
    echo "nginx 전체 access 로그를 검색합니다."
    grep -Rhs '/.well-known/acme-challenge/' /var/log/nginx/*access*.log 2>/dev/null |
        tail -n 100 || true
fi

if [[ -f "$ERROR_LOG" ]]; then
    echo
    echo "--- $ERROR_LOG ---"
    tail -n 100 "$ERROR_LOG" || true
fi

section "15. Certbot staging HTTP-01 실제 검증"

if [[ "$RUN_CERTBOT" == "1" ]]; then
    if ! command_exists certbot; then
        fail "Certbot이 없어 실제 검증을 실행할 수 없습니다."
    else
        TCPDUMP_PID=""

        if [[ "$CAPTURE_TCP" == "1" ]]; then
            if command_exists tcpdump && [[ -n "$PRIMARY_IFACE" ]]; then
                echo "TCP 80 패킷 캡처 시작: $PCAP_FILE"
                tcpdump -ni "$PRIMARY_IFACE" -nn -s0 \
                    -w "$PCAP_FILE" 'tcp port 80' >/dev/null 2>&1 &
                TCPDUMP_PID="$!"
                sleep 2
            else
                warn "tcpdump 또는 주 인터페이스를 확인할 수 없어 캡처를 생략합니다."
            fi
        fi

        CERTBOT_ARGS=(
            certonly
            --webroot
            -w "$WEBROOT"
            -d "$DOMAIN"
            --staging
            --preferred-challenges http
            --agree-tos
            --non-interactive
            --no-eff-email
            --verbose
        )

        if [[ -n "$CERTBOT_EMAIL" ]]; then
            CERTBOT_ARGS+=(--email "$CERTBOT_EMAIL")
        else
            CERTBOT_ARGS+=(--register-unsafely-without-email)
        fi

        echo
        echo "+ certbot ${CERTBOT_ARGS[*]}"
        set +e
        certbot "${CERTBOT_ARGS[@]}"
        CERTBOT_RC=$?
        set -e

        if [[ -n "$TCPDUMP_PID" ]]; then
            kill "$TCPDUMP_PID" 2>/dev/null || true
            wait "$TCPDUMP_PID" 2>/dev/null || true
            echo "패킷 캡처 저장: $PCAP_FILE"
        fi

        if [[ "$CERTBOT_RC" -eq 0 ]]; then
            ok "Certbot staging HTTP-01 검증이 성공했습니다."
        else
            fail "Certbot staging HTTP-01 검증이 실패했습니다. 종료 코드: $CERTBOT_RC"
        fi

        echo
        echo "--- 최근 Certbot 핵심 로그 ---"
        grep -nE \
            'secondary validation|Timeout during connect|challenge|validation|unauthorized|connection' \
            /var/log/letsencrypt/letsencrypt.log 2>/dev/null |
            tail -n 250 || true

        if [[ -f "$PCAP_FILE" && -s "$PCAP_FILE" ]]; then
            echo
            echo "--- 캡처된 TCP 80 SYN/SYN-ACK 요약 ---"
            tcpdump -nn -tttt -r "$PCAP_FILE" \
                'tcp[tcpflags] & tcp-syn != 0' 2>/dev/null |
                tail -n 300 || true
        fi
    fi
else
    warn "RUN_CERTBOT=0이므로 실제 Certbot staging 검증은 생략했습니다."
    echo "실제 검증 실행:"
    echo "  sudo RUN_CERTBOT=1 CAPTURE_TCP=1 bash $0 $DOMAIN $WEBROOT"
fi

section "16. 자동 판정"

echo "DNS A:              ${DNS_A:-없음}"
echo "공인 IPv4:          ${PUBLIC_IPV4:-확인 실패}"
echo "로컬 ACME HTTP:     ${HTTP_CODE_LOCAL:-확인 실패}"
echo "도메인 ACME HTTP:   ${HTTP_CODE_DOMAIN:-확인 실패}"
echo

if [[ -n "$PUBLIC_IPV4" && -n "$DNS_A" ]] &&
   ! grep -Fxq "$PUBLIC_IPV4" <<<"$DNS_A"; then
    fail "DNS A 불일치: 먼저 DNS 레코드를 수정해야 합니다."
elif [[ "$HTTP_CODE_LOCAL" != "200" || "$HTTP_BODY_LOCAL" != "$TEST_TOKEN" ]]; then
    fail "nginx/webroot 매핑 문제입니다. server_name, root, ACME location을 확인하십시오."
elif [[ "$HTTP_CODE_DOMAIN" != "200" || "$HTTP_BODY_DOMAIN" != "$TEST_TOKEN" ]]; then
    warn "도메인 경유 ACME 접근이 실패했습니다. DNS, IPv6, 상위 네트워크 또는 loopback 경로를 확인하십시오."
else
    ok "DNS, nginx, webroot, 로컬 HTTP-01 경로는 정상으로 보입니다."
    echo
    echo "이 상태에서 Certbot이 'During secondary validation: Timeout during connect'로 실패하면,"
    echo "Certbot 또는 challenge 디렉터리 문제가 아니라 일부 Let’s Encrypt 검증 노드와"
    echo "서버 사이의 상위 네트워크/해외 라우팅/DDoS 필터 문제일 가능성이 큽니다."
fi

section "17. 완료"

echo "진단 보고서:"
echo "  $REPORT_FILE"

if [[ -f "$PCAP_FILE" && -s "$PCAP_FILE" ]]; then
    echo "패킷 캡처:"
    echo "  $PCAP_FILE"
fi

echo
echo "테스트 challenge 파일은 자동 삭제됩니다."

Youez - 2016 - github.com/yon3zu
LinuXploit