문자 발송 공격

간단 한 소개
한 회사 가 문자 인증 코드 를 사용 해 사용자 의 진실성 을 검증 한 이후 문자 메 시 지 는 회사 업무 의 표지 가 되 었 다.현재 거의 모든 회사 의 서비스 에는 문자 발송 이라는 기능 이 포함 되 어 있다.사용자 가 문 자 를 요청 할 때 보통 등록 되 지 않 았 기 때문에 이 인 터 페 이 스 는 익명 인터페이스 (로그 인 필요 없 음) 입 니 다.
그 러 자 나 쁜 놈 들 이 소란 을 피 우기 시작 했다.그들 은 소프트웨어 팩 을 통 해 사용자 의 요청 메 시 지 를 받 은 다음 에 사용자 가 서버 에 미 친 듯 이 이 요청 을 보 내 는 것 을 모 의 했다. 회사 의 문자 자원 을 소모 하고 무고 한 핸드폰 사용 자 를 괴 롭 히 며 심지어 문자 채널 이 막 혀 정상 적 인 메 시 지 를 보 내지 못 했다.어쩌면 괴롭힘 을 당 한 사람들의 고소 에 회사 의 문자 통 로 를 차단 하거나 각 휴대 전화 업 체 에 의 해 스 팸 메시지 로 인식 되 어 마 케 팅 효 과 를 잃 거나 정상 적 인 업무 에 영향 을 미 칠 수도 있다.
오늘 나 는 이런 말썽꾸러기 를 만 났 다.그의 손 에는 약 수천 대의 육계 가 있다.그래서 우 리 는 즉시 이 일 을 처리 하기 시작 했다.
해결 책
우 리 는 두 가지 전략 을 생각 해 냈 다.
  • iptables 로 IP 봉 하기
  • nginx lua + redis 사용
  • 두 가지 방안 은 각각 좋 은 점 이 있 습 니 다. 방문 량 이 많 지 않 으 면 하 나 를 선택 하면 됩 니 다. 방문 량 이 매우 많 으 면 고려 해 야 합 니 다.
    우리 가 채택 한 전략 은 두 번 째 이다.
  • nginx 에 포 함 된 lua 프로그램 을 사용 하여 사용자 요청 (문자 만 가능) + IP 를 제한 합 니 다.
  • 1 분 내 요청 은 문자 1 회 만 보 낼 수 있다
  • 1 분 안에 1 회 를 초과 하 되 3 회 이내 에 서 는 문 자 를 보 내지 않 고 경고
  • 1 분 안에 3 회 를 초과 하면 이 IP 의 어떠한 방문 도 금지 합 니 다
  • nginx 가 전역 변 수 를 정의 할 수 없 도록 IP 와 시한 이 있 는 인터페이스 요청 을 redis 로 저장 합 니 다.이렇게 하면 nginx 는 실현 할 수 있 습 니 다.
  • IP 허용 여부 검사
  • 1 분 전 IP 접근 기록 자동 만 료

  • 처음에 우 리 는 첫 번 째 IP 를 사용 하여 이 IP 를 iptables 에 직접 넣 었 습 니 다. 커 널 차원 에서 이 IP 를 막 았 습 니 다. 다만 우 리 는 봉인 을 해제 하 는 것 이 비교적 번 거 롭 고 우리 의 소프트웨어 와 통합 할 수 없다 고 생각 했 습 니 다. 특히 논리 적 통합 (인 터 페 이 스 를 구별 하여 1 분 에 1 번, 3 번 이상 허용 해 야 IP) 을 실현 하기 어렵 고 nginx + redis 방안 을 사 용 했 습 니 다.루 아 프로그램 은 내 가 원 하 는 논리 로 쓸 수 있 으 니까.
    이루어지다
    만약 우리 가 문 자 를 보 낸 인터페이스 가 /sendSms ngix. conf 파일 server 세그먼트 에 정규 표현 식 location 과 일치 하도록 새 ~* sendSms 블록 이 라면 코드 를 쓰기 시작 합 니 다.
            location ~* sendSms { 
                    default_type 'application/json; charset=UTF-8';
                    lua_need_request_body on;
                    access_by_lua ' --   lua        ,             ,           ';
                    proxy_pass http://app_backend;
                    proxy_set_header X-Forwarded-For $remote_addr;
                    proxy_pass_header Origin;
            }

    아래 lua 코드 는 위의 작은 따옴표 로 복사 하 십시오.
    -- ngx.exit(ngx.OK)
    local cjson = require "cjson"
    local ip = ngx.var.remote_addr
    if ngx.var.http_user_agent ~= nil or ngx.var.http_user_agent == "" then
            local agent = string.lower(ngx.var.http_user_agent)
            -- ngx.say("user agent:", ngx.var.http_user_agent)
            -- ngx.exit(200)
            local sIdx = string.find(agent, "httpclient") or string.find(agent, "java")
            -- ngx.say("sIdx:", sIdx)
            if (sIdx ~= nil) then
                    ngx.status = ngx.HTTP_FORBIDDEN
                    local msg = "       ,              ,     ,     "
                    ngx.say(cjson.encode({code=16, msg=msg, R=cjson.null}))
                    ngx.exit(ngx.status)
                    return
            end 
    end
    local redis = require "resty.redis"
    local red = redis.new()
    
    red:set_timeout(1000)
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
            ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
            ngx.say("failed to connect: ", err)
            return
    end 
    
    local forbidden, err1 = red:sismember("fbdIP", ip) 
    -- ngx.say("forbidden:", forbidden)
    if forbidden == 1 then
            ngx.status = ngx.HTTP_FORBIDDEN
            local msg = "       ,              ,     ,     "
            ngx.say(cjson.encode({code=16, msg=msg, R=cjson.null}))
            ngx.exit(ngx.status)
            return
    end 
    
    local key = "ip::" .. ip
    -- ngx.say("key:", key)
    local ttl, err1 = red:ttl(key)
    if ttl == -1 then
            red:del(key)
    end
    
    local res, err = red:get(key)
    red:incr(key)
    if (not res) or (res == ngx.null) then
            --[[ local msg = "failed to get cache" ngx.say(cjson.encode({code=16, msg=msg, R=cjson.null})) --]]
            red:expire(key, 55) -- 55       IP  30   
            ngx.exit(ngx.OK)
    elseif tonumber(res) < 1 then
            ngx.exit(ngx.OK)
    elseif tonumber(res) >= 1 then
            ngx.status = 200 
            -- ngx.say("redis result is string 1")
            -- local msg = "             ,             "
            local msg = "  ,       "
            ngx.say(cjson.encode({code=16, msg=msg, R=cjson.null}))
           if tonumber(res) > 10 then
                    red:sadd("fbdIP", ip)
            end
            ngx.exit(ngx.status)
            return
    end

    해명 하 다.
  • 앞에서 key 에 대한 검 사 는 get() 뿐만 아니 라 ttl() 도 사 용 했 습 니 다. redis 의 만 료 회수 전략 은 LRU 와 비슷 한 알고리즘 을 사용 하여 일정 확률 로 삭제 되 지 않 기 때문에 ttl 을 사용 하여 검 사 했 습 니 다.

  • 전제 조건
    여러분 이 이 글 을 읽 을 때, 나 는 이 방안 이 적용 되 는 전 제 를 말씀 드 리 겠 습 니 다.만약 이런 전제 가 없다 면 이 방안 은 너 에 게 사용 할 수 없다. 물론 생각 을 해결 하 는 데 도움 이 될 수 있 을 것 이다. 만약 에 네가 손 을 잘 쓰 면 곧 자신의 해결 방법 을 잘 할 수 있 을 것 이다.우리 의 시스템 은 다음 과 같은 몇 가지 조건 을 만족시킨다.
  • 당연히 nginx 를 전단 대리 로 하 는 웹 구조
  • lua 를 지원 하 는 nginx 를 사용 합 니 다.nginx 홈 페이지 에서 직접 다운로드 하 는 nginx 는 지원 되 지 않 습 니 다. lua 코드 를 추가 로 다운로드 하고 nginx 에 컴 파일 해 야 합 니 다.또는 openresty
  • 를 직접 사용 합 니 다.
    건의 하 다.
    이전에 redis 는 루트 권한 을 얻 을 수 있 는 구멍 이 있 었 기 때문에:
  • redis 에 접근 비밀 번 호 를 설정 해 야 합 니 다
  • redis 서비스의 바 인 딩 IP 를 내부 네트워크 IP 에 한정 하 는 것 이 좋 습 니 다
  • .
    redis 의 key 는 절대 오염 되 어 서 는 안 됩 니 다. 그렇지 않 으 면 정상 적 인 사용자 의 IP 가 잘못 손상 되 어 금 지 됩 니 다.
    공구.
    이 문 제 를 해결 할 때, 우 리 는 또한 많은 뇌 세 포 를 폐 기 했 습 니 다. 당신 의 뇌 세 포 를 절약 하기 위해 서, 나 는 무료 로 당신 에 게 보 여 드 리 겠 습 니 다.
    나 쁜 놈 이 내 서버 를 공격 하고 있다 는 걸 어떻게 알았지?
    나의 방법 은 로그 에 있 는 모든 /sendSms 인터페이스의 호출 IP 를 통계 함으로써 호출 횟수 가 비교적 많은 것 을 찾 는 것 이다. 예 를 들 어 10 번 이상 인 것 이다.이 명령 을 사용 하면 됩 니 다.
    grep  "POST /sendSms" logs/ikuaiyue.log | awk '{print $6}' | sed s/IP:// | sort | uniq -c | awk '{print $1 "\t" $2}' | sort -n

    그리고 이런 결 과 를 볼 수 있다.
    1       115.205.13.179
    2       117.136.40.20
    2       117.136.94.44
    2       117.59.39.22
    122     223.104.10.28

    첫 번 째 열 은 이 IP 의 호출 횟수 입 니 다. 두 번 째 열 은 알 고 있 습 니 다.자, 이제 고 치 는 법 알 겠 지?
    참고 로 우리 의 일 지 는 이 모양 입 니 다.
    [2016-05-30 01:25:20.451] [INFO] normal - IP:117.174.26.32 POST /sendSms
    [2016-05-30 01:26:17.918] [INFO] normal - IP:117.174.26.32 POST /sendSms
    ...

    위의 명령 을 조금 설명 하 세 요:
    grep  "POST /sendSms" logs/ikuaiyue.log 
     | awk '{print $6}'       #        6 ( IP:117.174.26.32)
     | sed s/IP://            #    "IP:"
     | sort                   #  
     | uniq -c                #  ,      ,          
                              #          ,IP       .
                              #          :         
     | awk '{print $1 "\t" $2}'  #        ,      ,  tab  
     | sort -n                #        。-n         ,         

    당신 이 나의 방법 에 따라 nginx 를 설정 한 후에 효과 가 있 는 지 없 는 지 어떻게 압 니까?
    방금 그 명령 을 고치 고 마지막 3000 줄 만 출력 합 니 다.
     tail -n3000 logs/ikuaiyue.log | grep  "POST /sendSms" | awk '{print $6}' | sed s/IP:// | sort | uniq -c | awk '{print $1 "\t" $2}' | sort -n

    좋은 웹페이지 즐겨찾기