[번역] 스팸과 싸우는 5가지 방법

원본글 : 5 Ways of Battling Form Spam

만약 여러분이 웹 어플리케이션을 만든다면, 스팸 봇들에게 점렴당하는 것들을 막을순 없을것입니다.

내 프로젝트 중 하나는 회원가입 폼 부분을 당해서 17,000여개의 가짜 유저을 정리하는 것은 정말 힘들었습니다.
이를 해결 하기위해 스팸 봇과 싸우는 방법을 트위터에 물어보았고, 많은 반응과 좋은 아이디어들을 얻을 수 있었습니다.
나는 언급된 방법들을 공유해 만약 여러분이 공격 당했을 때 어떻게 방어해야하는지 알게 될 수 있을 것입니다.

1. Cloudflare

만약 여러분이 클라우드플레어와 같은 서비스를 사용하고 있으면, “page rules”을 설정해 URL과 양식 제출을 중단 할 수 있습니다.

다만, 나는 잘 다른 방법들 처럼 잘 작동 하지 않을 것 이라고 생각합니다.
하지만, 이 방법은 이미 운영중인 서비스에 어떠한 영향도 없이 신속하게 구현이 가능합니다.

2. Honeypot

허니팟은 최우선 방어 방법이고 설치 또한 간단합니다.
폼 안에 아무런 값이 없는 hidden 타입에 input 태그를 두고, 만약 hidden 필드 안에 값과 함께 제출 된다면, 거의 봇일 것이다.

직접 이를 구현 할 수 있지만, spatie/laravel-honeypot과 같은 패키지가 조내하여 손쉽게 통합된 환경에서 구현 할 수 있습니다

‘name’이라는 input필드가 있다면 봇은 그 필드를 체워야 하는 필드로 생각한다는 것을 기억하시기 바랍니다.

3. Validate Emails(이메일 인증)

만약 이메일을 요구하는 양식이라면, 사용자 이메일로 메일을 보내어 이메일 안에 있는 버튼을 클릭하여 진짜인지 확인 할 수 있습니다.
이 방법은 Laravel Newsletter를 포함해 많은 뉴스레터가 사용하는 방식입니다.

사용자는 이메일을 입력하고, 우리의 서비스는 이메일을 보내면, 사용자는 승인버튼을 눌러 우리의 서비스에 가입 할 수 있다.

이렇게 하면 가입되는 유저는 봇이 아니라 실제 사용자들일 것 입니다.

다른 방법으론 API를 통해 이메을 확인해주는 identibyte와 같은 서비스를 사용하는 방법입니다.
이런 방법을 통해 사용자들은 추가적인 단계없이 가입 할 수 있습니다.

4. Capcha

이 방법은 제가 가장 좋아하는 방법입니다. 왜냐하면, 내가 캡차를 싫어하기 때문입니다.
Google가 가지고 있는 invisible reCaptcha는 아마 제일 좋은 캡차 옵션일 것입니다.

5. Dedicated Spam Services (스팸 차단 서비스)

두 가지의 유명한 스팸 서비스인 AkismetStop Forum Spam이 있습니다.
Akismet은 유료이고 Stop Forum Spam은 무료 입니다.

나는 Akismet 모든 워드프레스 사이트에 사용했습니다. 하지만, 워드프레스가 아닌 라라벨에서도 사용할 수 있습니다.
여러분은 들은 Akismet의 API를 사용하거나 nickurt/laravel-akismet과 같은 것을 사용하면 됩니다.

Stop Forum Spam은 무료 서비스 이며, Akismet와 유사합니다.
그리고 라라벨 페키지 nickurt/laravel-stopforumspam가 존재하며, 통합 적으로 사용할 수 있게 도움을 줍니다.


모든 옵션들은 honeypot을 해본 후 부터 사용하는것이 좋습니다.
만약 honeypot이 실패하면 봇이 공격을 막을때 까지 다른 옵션들을 사용하세요.
다만, 오늘 동작하는것이 내일엔 안할수도 있다는 것을 기억하고, 매일 봇들과 싸워야 한다는것을 잊지 마세요.

[Clipping] php-fpm slow log

서비스 응답속도 저하시 php-fpm 로그로 원인분석하기

원글: HeuJung

php-fpm 기반의 웹 서비스에서 알 수 없는 문제로 응답속도가 저하되는 현상이 발생 할 경우가 있다.

원인은 여러가지일 수 있다. 몇 가지를 꼽자면,

  1. DBMS의 SQL 응답속도 저하 (Slow Query)
  2. 네트워크 지연
  3. 서버 리소스 점유율 (CPU, RAM 등)
  4. Disk 입출력 문제

등이 있다.

하지만, 서버 리소스나 네트워크에 별 다른 문제가 없고, Slow Query 로그에도 별다른 이상징후가 없다면,
원인분석을 해야 하는데, 이 경우 Web Application에서 어느 로직에서 문제가 발생하는지 원인을 찾아야 한다.

개발환경이라면 Xdebug를 활용 수 있겠지만, 실 서비스 중인 서버에는 적용할 수 없는 방법이다.

이 때는 php-fpm의 slowlog를 찍어서 확인하는 방법이 있다.

php-fpm.conf 파일을 열어보면 아래와 같은 설정을 확인할 수 있다.

......
request_slowlog_timeout = 30s
slowlog = /var/log/php-fpm/slow.log
......

여기서 request_slowlog_timeout을 3~5s 정도로 낮추고, php-fpm 서비스를 재시작한다.

그 후 tail 명령으로 /var/log/php-fpm/slow.log 를 확인 후 응답속도가 느린 페이지에 접속을 계속 시도해보면 다음과 같은 로그가 찍힌다.

[10-Nov-2018 11:30:22]  [pool www] pid 8260
script_filename = ******************
[0x00007f1a47de79a8] fopen() ****.php:422
[0x00007f1a47de6500] +++ dump failed

이 로그에는 응답속도 저하의 원인이 되는 파일과 해당 function까지 추적해주기 때문에 원인을 금방 파악할 수 있다.

[VS code] php xdebug 설치

최근 VScode에서 제공하기 시작한 Remote-WSL를 사용하기 시작하면서 효과를 톡톡히 보고 있는 와중에 윈도우에선 복잡했던 php xdebug 설정을 통해 laravel 프로젝트에서 테스트 해보고자 한다.

먼저, WSL상에서 phpinfo() 또는 php -i의 결과값을 https://xdebug.org/wizard 에 붙여넣은 뒤 나오는 방법 데로 xdeubg를 설치하고 phpinfo() 또는 php -i의 결과에서 xdebug 가 설치 되어있는지 확인하자.

만약 xdebug 설치중 phpize가 없다면 apt-get install php-dev와 같이 패키지 관리자로 php-dev 확장자를 설치해주면 된다.

wsl상의 php에 xdebug가 설치됨
php ini 에 zend_extension 뿐 아니라 xdebug remote 옵션도 켜져 있어야한다.

xdebug가 설치 되었음을 확인했으면 vscode에서 php debug 확장을 설치해준다.

이제 vscode를 실행 시킬 디렉토리에서
1. code . 를 입력하여서 Remote-wsl이 활성화된 vscode를 실행 시키고
2. ctrl+shit+d를 눌러 debug 창을 연 뒤
3. 상단의 톱니 바퀴를 누르고 PHP를 선택하면 .vscode 폴더와 launch.json 파일이 생성되며 디버깅할 준비가 완료되었다.

이제 F5 를 눌러 디버깅을 실행 시키고 원하는 위치에 break 포인트를 찍은 뒤 php를 실행시키면,

위와 같이 디버깅이 실행 되는 것을 확인할 수 있다.

[PHP] Multiple for(each), while escape

TL;TR;

PHP는 중접된 반복문을 원하는 개수만큼 탈출할 수 있는,
break 2;continue 2;를 지원한다.

다중 for문

PHP가 아니더라도 어디에든 다중 for문을 사용하는 것을 본적이 있을 것이다.
물론, for문이 중첩되어있는걸 보면 경기를 일으키는 사람도 있지만,
어쩔 수 없이 써야할수도 있고, 지식의 한계와 마감의 촉박함이 다중 for문을 만들어 낼 수 있는 부분은 어쩔수 없다.

뭐 그렇다치고, 모던 PHP그룹을 보던중 신기한(?) 문법을 발견해 정리겸 글을 남긴다.

어마아머한 3중 for문

<?php
$list_0 = [0, 1];
$list_1 = [2, 3];
$list_2 = [4, 5];
foreach ($list_0 as $v_0) {
    echo $v_0;
    foreach ($list_1 as $v_1) {
        echo $v_1;
        foreach ($list_2 as $v_2) {
            echo $v_2;
        }
    }
}

보통 이런 for문을 보면 현기증 부터오고 개발자의 얼굴을 보고 싶어지지만, 일단 넘어가기로 하고, 해당 코드를 실행하면 02453451245345 가 출력된다.

여기서 만약 3번째 for문 에서 break; 를 하게되면 당연스럽게도 2번째 for문으로 넘어가게된다.

하지만 3번째 for문 이후 첫번째 for문으로 넘어가기 위해선 아래와 같이 해주어야 한다.

<?php
$list_0 = [0, 1];
$list_1 = [2, 3];
$list_2 = [4, 5];
foreach ($list_0 as $v_0) {
    echo $v_0;
    foreach ($list_1 as $v_1) {
        echo $v_1;
        foreach ($list_2 as $v_2) {
            echo $v_2;
            break;
        }
        break;
    }
}

지금과 같이 단순한 경우엔 문제가 없지만, “만약 $v_2가 짝수이면 첫번째 for문으로 가고 홀수이면 그냥 진행 하고 싶다.”과 같은 조건문이 생기게 된다면 아래와 같이 복잡한 코드가 될것이다.

<?php
$list_0 = [0, 1];
$list_1 = [2, 3];
$list_2 = [4, 5];
foreach ($list_0 as $v_0) {
    echo $v_0;
    foreach ($list_1 as $v_1) {
        echo $v_1;
        foreach ($list_2 as $v_2) {
            echo $v_2;
            if ($v_2 % 2 === 0) break;
        }
        if ($v_2 % 2 === 0) break;
    }
}

if ($v_2 % 2 === 0) break;가 반복되면서 안그래도 복잡한 코드를 더욱 복잡하게 만들고있다. 또 이후에 홀수일때 break 하고 싶다면, 해당 구문을 모두 찾아 바꿔줘야하는 번거로움까지 있다.

이럴 때 간단히 원하는 개수만큼 break; 해주는 php문법인 break 2;를 사용할 수 있다.

<?php
$list_0 = [0, 1];
$list_1 = [2, 3];
$list_2 = [4, 5];
foreach ($list_0 as $v_0) {
    echo $v_0;
    foreach ($list_1 as $v_1) {
        echo $v_1;
        foreach ($list_2 as $v_2) {
            echo $v_2;
            if ($v_2 % 2 === 0) break 2;
        }
    }
}

이렇게 하면 만약 $v_2가 짝수일때 break 2;를 통해 바로 첫번째 for문까지 바로 점프하게 된다.

물론 break말고 continue도 위와같이 중첩된 반복문에서 원하는 개수만큼의 반복문을 탈출 할 수 있는 continue 2;문법을 지원한다.

[PHP] Distinction Null and Undefined

TL;TR

<?php array_key_exists('value', get_defined_vars()) ?>


대게 주의에선 PHP에선 Null과 Undefined를 구별하지 못한다고 알려져 있다.
나 또한 Null과 Undefined를 구별할 수 있는 방법이 없다고 생각했지만, 성능상의 이슈를 제외하고, PHP에서도 Null과 Undefined를 구분 할 수 있다.

<?php 
    $a = null;
    var_dump(isset($a), isset($b));
    // Result : bool(false) bool(false)
    var_dump($a, $b);
    // Result : NULL NULL
?>

PHP에서 변수의 유효성을 확인하기 위해 가장 많이 사용되는 함수인 isset은 해당변수가 ‘정의 되어 있지 않’거나 변수의 값이 ‘Null’인 경우에 false를 반환한다.

즉, php의 변수의 유효성을 판단하기 위한 함수은 isset은 ‘정의되지 않음’과 ‘Null’을 구별 하지 못한한다.

또한, 6번째 줄의 결과값을 보면 $a$b의 출력값은 NULL로 동일하다.
(물론 php Notice가 b는 선언되어있지 않다고 알경고를 띄워주기는 한다.)

JS와 같이 php와 같이 공부를 많이하는 언어의 경우
undefined라는 타입이 있으며, value == undefined와 같이 사용이 가능하기에
php에선 구분이 불가능하다 라고 알려진것같다.
(적어도 나는 이렇게 생각했다…)

하지만 PHP에서도 undefined와 Null을 구별할 수 있는데 이는 get_defined_vars()를 사용해 현재 선언된 변수들의 목록에서 찾는 방법이다.

<?php
    $a = null;
    var_dump(array_key_exists('a', get_defined_vars()), array_key_exists('b', get_defined_vars()));
    // Result : bool(true) bool(false)

    unset($a);
    var_dump(array_key_exists('a', get_defined_vars()));
    // Result : bool(false)

퍼포먼스 상으론 그렇게 좋은 방식은 아닌것 같지만, 이를 통해서 해당 변수가 정의되어있는지 단순히 null인지 확인 할 수 있는 확실한 방법이다.
(이외의 다른 방법이 있다면 댓글로..)

[Linux] Power management

우분투 서버 18.04에서 노트북 모니터를 닫아도 잠금 모드로 전환 되지 않는 설정


$ vi /etc/systemd/logind.conf

edit #HandleLidSwitch=suspend To HandleLidSwitch=ignore

sudo service systemd-logind restart

만약 화면을 닫았을 경우 자동으로 잠기길 원한다면

HandleLidSwitch=lock으로 변경하면 된다.

[iptables] iptables를 이용한 국가별(특히 중국) IP ban 방법

IPTABLES 명령어를 이용한 국가 ip 벤 방법


여러대의 서버를 사용하면서 well know 포트가 하나라도 열려있으면 미친듯하게 접속 시도가 들어온다. (특히 중국에서 ㅂㄷㅂㄷ)
이를 iptables를 통해 국가별 ip를 차단하자

GITHUB LINK
https://github.com/SilNex/SundryToolBox/blob/master/country-ip-ban.sh

#!/bin/bash
#China IP BAN using iptables

apt-get update && apt-get install wget unzip
wget http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip
unzip GeoIPCountryCSV.zip

DATA=./GeoIPCountryWhois.csv

for IPRANGE in `egrep "CN" $DATA | cut -d, -f1,2 | sed -e 's/"//g' | sed -e 's/,/-/g'`
do
    echo $IPRANGE
    iptables -A INPUT -p all -m iprange --src-range $IPRANGE -j DROP
done

https://dev.maxmind.com/geoip/legacy/geolite/에서 지역별로 정리한 ip리스트 파일을 다운 받아,
10번째 줄에 “CN” 이란 지역명을 가진 ip를 모두 차단 한다.

만약 미국을 차단한다라고 하면 “US” 러시아는 “RU”로 바꿔주면 된다.

잘 적용 되었는지 확인하려면,
iptables -L를 통해서 볼 수 있으며,
iptables를 초기화 하고 싶다면 iptables -F를 통해 초기화 할 수 있다.

그외 자세한 사항은 iptables 설명서를 참고

[WordPress] WP PHP extension requirements

cURL – 127 uses (requires libcurl)

  • HTTP API (class WP_Http_curl)
  • url_is_accessable_via_ssl()
  • SimplePie (overridden with class WP_SimplePie_File)
  • GoogleSpell (from TinyMCE package, is not used?)

Date/Time – 367 uses

DOM – 6 uses (requires libxml)

  • iis7_rewrite_rule_exists()
  • iis7_delete_rewrite_rule()
  • iis7_add_rewrite_rule()
  • saveDomDocument()

POSIX Regex – 23 uses

Filter – 2 uses

  • class PHPMailer->ValidateAddress() (optional)

FTP – 72 uses

  • class ftp_base
  • class ftp (pure and sockets versions)
  • class WP_Filesystem_FTPext
  • class WP_Filesystem_ftpsockets

GD – 56 uses

  • wp-admin\includes\image-edit.php
  • wp-admin\includes\image.php
  • wp-includes\media.php

Hash – 6 uses

  • wp-includes\pluggable.php multiple uses (optional – fallback in wp-includes\compat.php)

iconv – 5 uses

  • class SimplePie (optional)
  • wp_check_invalid_utf8() (optional)
  • wp-mail.php (optional)

JSON – 20 uses

  • optional, fallbacks in wp-includes/compat.php

libxml – 4 uses

  • class WP_oEmbed->_parse_xml() (optional)
  • SimplePie

Multibyte String – 29 uses

  • some fallback in wp-includes/compat.php

MySQL – 60 uses

  • class wpdb
  • class SimplePie_Cache (overridden with class WP_Feed_Cache)

OpenSSL – 4 uses

  • class PHPMailer

PCRE – 743 uses

SimpleXML – 1 uses

  • class WP_oEmbed (seems optional)

Sockets – 64 uses

  • class ftp (sockets implementation)

SPL – 3 uses

  • Tokenizer – 3 uses

    • wp_doc_link_parse() (optional)

XML Parser – 89 uses

XMLReader – 1 uses

  • SimplePie (seems optional)

Zlib – 30 uses