Google reCAPTCHA PHP (CURL/Snoopy) Sample

logo_recaptcha_color_24dp Google reCAPTCHA

입력폼

[code lang=”html”]
<form onsubmit="return check_form()">
<input type="hidden" id="recaptcha_response" name="recaptcha_response" value="" />
<div id="recaptcha1"></div>
<button type="submit">확인</button>
</form>
[/code]

[code lang=”javascript”]
<script type="text/javascript">
var recaptchaWidger1;
var onloadCallback = function() {
recaptchaWidger1 = grecaptcha.render(‘recaptcha1’, {
‘sitekey’ : ‘{Site key}’
});
};
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>

<script>
function check_form() {
var recaptch_response = grecaptcha.getResponse(recaptchaWidger1);
if (!recaptch_response) {
alert("자동가입방지 문자를 확인해 주세요");
return false;
}

document.getElementById("recaptcha_response").value = recaptch_response;

return true;
}
</script>
[/code]

폼데이터 처리(1) – Snoopy.lib.php

[code lang=”php”]
include_once ‘Snoopy.class.php’;
$snoopy = new Snoopy;
$data = array(
"secret" => "Secret key",
"response" => $_POST[‘recaptcha_response’]
);
$snoopy->submit(‘https://www.google.com/recaptcha/api/siteverify’, $data);
$response = json_decode($snoopy->results);
if (!$response->success) {
exit("fail message");
}
[/code]

폼데이터 처리(2) – CURL

[code lang=”php”]
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, "secret={Secret key}&response=".$_POST[‘recaptcha_response’]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec ($ch);
curl_close ($ch);
$response = json_decode($output);
if (!$response->success) {
exit("fail message");
}
[/code]

find 명령으로 오래된 파일찾기

find 명령으로 오래된 파일을 찾아본다

현재위치에서 *.txt파일중 30일이 지난 파일을 출력

# find . -name “*.txt” -type f  -ctime +30 -print

현재위치에서 30일이 지난 모든 파일을 삭제

# find . -type f -ctime +30 | xargs rm

현재위치에서 30일이 지난 모든 디렉토리를 삭제

# find . -type d -ctime +30 | xargs rm -rf

현재 위치에서 7일안에 생성된 파일을 출력

# find . -type f -ctime -7 -print

현재 위치에서 1일안에 수정된 파일을 출력

# find . -type f -mtime -1 -print

옵션설명

-type f(파일) d(디렉토리) l(링크)

-ctime : 생성시간

-mtime : 수정시간

-atime : 접근시간

100kb이상인 파일을 출력

# find . type f -size +100k -print

fail2ban – 워드프레스 로그인 차단 (wp-login.php)

워드프레스 악의적인 로그인 차단

fail2ban 을 이용하여 워드프레스(https://wordpress.org/) 로그인 페이지를 안전하게 해보자

1

어느날 갑자기 무차별대입공격(brute force attack)으로 로그가 엄청나게 늘어나 버렸다.

워드프레스에 Wordfence 플러그인이 설치되어 있었지만 차단해주지는 못했다.

때문에 로그파일을 읽어 방화벽(iptables)에 등록해주는 fail2ban 을 이용하여 직접 방어해본다.

 

기본환경

ubuntu 14.04에 apache2가 설치되어 있다.

 

fail2ban 설치하기

fail2ban은 지정된 로그파일을 모니터링하면서 정규식을 이용, 특정패턴을 읽는다. 패턴이 일정이상 반복되면 bantime (초) 만큼 방화벽에 아이피를 등록하여 차단한다.

우분투 패키지매니저를 통한 설치

#sudo apt-get install fail2ban

소스설치

https://github.com/fail2ban/fail2ban 에서 소스파일을 다운로드한다.

로그파일 확인

워드프레스의 로그인 파일(wp-login.php)에 접근한 로그를 확인하기 위해 아파치 로그폴더를 확인해 본다

# cat /var/log/apache2/access.log | grep wp-login.php

91.200.12.42 – – [12/Sep/2016:02:02:49 +0900] “POST /wp-login.php HTTP/1.1” 200 5527 “http://jongwan.com/wp-login.php” “Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)”
91.200.12.42 – – [12/Sep/2016:02:03:07 +0900] “POST /wp-login.php HTTP/1.1” 200 5527 “http://jongwan.com/wp-login.php” “Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)”
91.200.12.42 – – [12/Sep/2016:02:03:14 +0900] “POST /wp-login.php HTTP/1.1” 200 5527 “http://jongwan.com/wp-login.php” “Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)”
91.200.12.42 – – [12/Sep/2016:02:03:25 +0900] “POST /wp-login.php HTTP/1.1” 200 5527 “http://jongwan.com/wp-login.php” “Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)”
91.200.12.42 – – [12/Sep/2016:02:03:37 +0900] “POST /wp-login.php HTTP/1.1” 200 5527 “http://jongwan.com/wp-login.php” “Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)”

wp-login.php 파일에 수초간격으로 계속 접근을 시도한 것을 확인할 수 있다.

 

fail2ban 룰 작성

fail2ban에서 사용하는 failregex 라는 룰을 작성한다. 로그파일을 확인해 보면 아래와 같이 작성할 수 있다.

^<HOST> .* “POST /wp-login.php

 

룰 파일을 생성

fail2ban 은 기본적으로 /etc/fail2ban 에 설치된다.

# vim /etc/fail2ban/filter.d/wordpress-login.php

파일 내용은 아래와 같다

[bash]

[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
ignoreregex =

[/bash]

 

jail.conf 에 룰을 등록

룰을 생성했드면 jail.conf 에 해당 룰을 등록해 줄 수 있다. /etc/fail2ban/jail.conf 파일을 열어 마지막줄에 아래 내용을 추가해준다.

# vim /etc/fail2ban/jail.conf

[bash]

[wp-auth]
enabled = true
filter = wordpress-login
action = iptables-multiport[name=NoAuthFailures, port="http,https"]
logpath = /var/log/apache2/*access*.log
bantime = 1200
maxretry = 8

[/bash]

fail2ban 룰 테스트

fail2ban은 아래와 같은 명령을 이용해서 룰을 확인할 수 있다.

#fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/wordpress-login.conf

 

fail2ban 재시작

#service fail2ban restart

NCDU – 리눅스에서 폴더별 용량 확인

NCDU

우분투에서 폴더의 사용량을 확인하기 위해 항상 du 명령어를 사용했었다.

간단하게 사용이 가능하지만 한눈에 알아보기 힘든 UI로 인해서 고민하다 비주얼하게 출력해주는 프로그램을 하나 만들어 볼까?? 하던차에 구글링을 해보니, 이미 많이 사용되는 프로그램이 있어 소개해본다

일단 콘솔에서 확인해야 하기에 바오밥(https://en.wikipedia.org/wiki/Disk_Usage_Analyzer) 의 경우 X윈도우가 필요하니 패스~

NCDU 설치방법

NCDU를 사용하기 위해서 우분투기준 아래와 같이 설치를 진행한다

#apt-get install ncdu
#ncdu /

실행하고 한참 탐색을 진행하다 마치면 아래와 같은 화면이 나온다. 폴더별로 사용량이 깔끔하게 출력된다.

NCDU 스크린샷1

NCDU 스크린샷2

 

jquery datepicker 한글화

<link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="http://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script>
jQuery(function($) {
  $.datepicker.regional['ko'] = {
    closeText : '닫기',
    prevText : '이전달',
    nextText : '다음달',
    currentText : '오늘',
    monthNames : ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
    monthNamesShort : ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
    dayNames : ['일', '월', '화', '수', '목', '금', '토'],
    dayNamesShort : ['일', '월', '화', '수', '목', '금', '토'],
    dayNamesMin : ['일', '월', '화', '수', '목', '금', '토'],
    weekHeader : 'Wk',
    dateFormat : 'yy-mm-dd (DD)',
    firstDay : 0,
    isRTL : false,
    showMonthAfterYear : true,
    yearSuffix : '년'
  };

  $.datepicker.setDefaults($.datepicker.regional['ko']);
});

$(document).ready(function(){
  $(".datepicker").datepicker();
});
</script>

업로드 디렉토리에서 웹쉘 php 실행 방지(webshell)

업로드 디렉토리에서 아래 명령을 실행

# find . -type f | xargs grep -n "eval("
웹쉘파일

파일은 gif형식인데 내용에는 php 코드가 들어있다.

POST로 데이터를 전송하면 eval() 명령으로 실행이 된다.

위와 같은 상황처럼 이미지에 들어있는 php코드가 실행이 되는 것을 방지하지 위해서

아예 업로드 디렉토리는 php파싱을 하지 않도록 하는 방법을 안내한다

/etc/httpd/conf/httpd.conf

<Directory /home/test/public_html/data/>
    php_admin_value engine Off
</Directory>

Solr 검색엔진을 데몬으로 등록 (update-rc.d)

Apache Solr 를 사용하는중에 시스템이 재부팅되면 다시 시작하는게 귀찮아서

구글링을 해보니 데몬으로 등록하는 방법이 있어 스크랩한다.

 

데몬설치

# sudo su

# apt-get install daemon

 

시작스크립트 생성

# cd /etc/init.d/

# cat > solr

[bash]
#!/bin/sh

# Prerequisites:
# 1. Solr needs to be installed at /usr/local/solr/example
# 2. daemon needs to be installed
# 3. Script needs to be executed by root

# This script will launch Solr in a mode that will automatically respawn if it
# crashes. Output will be sent to /var/log/solr/solr.log. A pid file will be
# created in the standard location.

start () {
echo -n "Starting solr…"

# start daemon
daemon –chdir=’/usr/local/solr/example’ –command "java -jar start.jar" –respawn –output=/var/log/solr/solr.log –name=solr –verbose

RETVAL=$?
if [ $RETVAL = 0 ]
then
echo "done."
else
echo "failed. See error code for more information."
fi
return $RETVAL
}

stop () {
# stop daemon
echo -n "Stopping solr…"

daemon –stop –name=solr –verbose
RETVAL=$?

if [ $RETVAL = 0 ]
then
echo "done."
else
echo "failed. See error code for more information."
fi
return $RETVAL
}

restart () {
daemon –restart –name=solr –verbose
}

status () {
# report on the status of the daemon
daemon –running –verbose –name=solr
return $?
}

case "$1" in
start)
start
;;
status)
status
;;
stop)
stop
;;
restart)
restart
;;
*)
echo $"Usage: solr {start|status|stop|restart}"
exit 3
;;
esac

exit $RETVAL
[/bash]

# update-rc.d solr defaults

중간에 solr가 설치되어 있는 폴더를 변경해줘야 한다.

 

logrotate 등록

# cd /etc/logrotate.d
# cat > solr

[bash]
/var/log/solr/*.log {
  weekly
  rotate 12
  compress
  delaycompress
  create 640 root root
  postrotate
    /etc/init.d/solr restart
  endscript
}
[/bash]

 

윈도우 방화벽 규칙 추가/삭제 – C#

방화벽 관련해서 참고는 구글링 웹문서들과 mdsn
https://msdn.microsoft.com/ko-kr/library/windows/desktop/aa365344(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ff956124(v=vs.85).aspx

c#에서 방화벽에 규칙을 추가하는 방법을 찾던중에
쉽게 할 수 있는 방법이 있어 해당 코드를 안내합니다.

참조추가

FirewallAPI.dll 파일을 참조로 추가합니다.

규칙추가

INetFwRule firewallRule = (INetFwRule)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FWRule"));
 firewallRule.Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW;
 firewallRule.Description = "방화벽 규칙에 대한 설명을 입력합니다";
 firewallRule.Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN;
 firewallRule.ApplicationName = Application.ExecutablePath + @"\app.exe";
 firewallRule.InterfaceTypes = "All";
 firewallRule.Name = "Rule name"; // 방화벽 규칙을 구분하는 이름, 삭제시에도 사용됩니다
 firewallRule.Enabled = true;
 INetFwPolicy2 firewallPolicy = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2"));
 firewallPolicy.Rules.Add(firewallRule);

규칙삭제

INetFwPolicy2 firewallPolicy = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2"));
 firewallPolicy.Rules.Remove("Rule name");

기존에 추가된 방화벽 규칙을 확인

INetFwPolicy2 firewallPolicy = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2"));
 foreach(INetFwRule rule in firewallPolicy.Rules)
 {
 Console.WriteLine(rule.Name);
 }

C#, PHP – RIJNDAEL 256bit 암/복호화

C#과 php간에 데이터를 주고 받아야 하는데
평문으로 통신하기에 무리가 있던 도중
기존에 사용하던 php 암호화 라이브러리와 연동되는 c# 코드를 발견~
살짝 수정해서 사용하는데 무리가 없음

기본은 128bit지만 256bit로 변경했음
php의 경우 블록에서 남는 부분을 zero로 채워넣기 때문에
c#에서 PaddingMode.Zeros를 설정해야 정상적으로 동작합니다.

Encryption.cs

[csharp]
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace TestClass
{
class Encryption
{
private static byte[] rijnKey = Encoding.UTF8.GetBytes("abcdefg_abcdefg_abcdefg_abcdefg_");
private static byte[] rijnIV = Encoding.UTF8.GetBytes("abcdefg_abcdefg_abcdefg_abcdefg_");

public static String EncryptIt(String s)
{
String result;
RijndaelManaged rijn = new RijndaelManaged();
rijn.Mode = CipherMode.CBC;
rijn.Padding = PaddingMode.Zeros; // php와의 연동에서 꼭 확인
rijn.BlockSize = 256;

using (MemoryStream msEncrypt = new MemoryStream())
{
using (ICryptoTransform encryptor = rijn.CreateEncryptor(rijnKey, rijnIV))
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(s);
}
}
}
result = Convert.ToBase64String(msEncrypt.ToArray());
}
rijn.Clear();

result = Base64UrlEncode(result);

return result;
}

public static String DecryptIt(String s)
{
String result;
RijndaelManaged rijn = new RijndaelManaged();
rijn.Mode = CipherMode.CBC;
rijn.Padding = PaddingMode.Zeros;
rijn.BlockSize = 256;

s = Base64UrlDecode(s);

using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(s)))
{
using (ICryptoTransform decryptor = rijn.CreateDecryptor(rijnKey, rijnIV))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader swDecrypt = new StreamReader(csDecrypt))
{
result = swDecrypt.ReadToEnd();
}
}
}
}
rijn.Clear();

return result;
}

public static string Base64UrlEncode(string arg)
{
arg = arg.Split(‘=’)[0]; // Remove any trailing ‘=’s
arg = arg.Replace(‘+’, ‘-‘); // 62nd char of encoding
arg = arg.Replace(‘/’, ‘_’); // 63rd char of encoding
return arg;
}

public static string Base64UrlDecode(string arg)
{
string s = arg;
s = s.Replace(‘-‘, ‘+’); // 62nd char of encoding
s = s.Replace(‘_’, ‘/’); // 63rd char of encoding
switch (s.Length % 4) // Pad with trailing ‘=’s
{
case 0: break; // No pad chars in this case
case 2: s += "=="; break; // Two pad chars
case 3: s += "="; break; // One pad char
default:
throw new System.Exception("Illegal base64url string!");
}
return s;
}
}
}

[/csharp]

test.php

[php]
//Encryption function
function encryption($encrypt, $key, $iv)
{
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, ”, MCRYPT_MODE_CBC, ”);
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $encrypt);
$encode = base64_encode($encrypted);
$encode = base64safeEncode($encode);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

return $encode;
}

//Decryption function
function decryption($decrypt, $key, $iv)
{
$decoded = base64safeDecode($decrypt);
$decoded = base64_decode($decoded);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, ”, MCRYPT_MODE_CBC, ”);
mcrypt_generic_init($td, $key, $iv);
$decrypted = mdecrypt_generic($td, $decoded);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

return trim($decrypted);
}

function base64safeEncode($data) {
$data = str_replace("=", "", $data);
$data = str_replace("+", "-", $data);
$data = str_replace("/", "_", $data);
return $data;
}

function base64safeDecode($data) {
$data = str_replace("-", "+", $data);
$data = str_replace("_", "/", $data);

switch (strlen($data) % 4) {
case 0: break;
case 2: $data .= "=="; break;
case 3: $data .= "="; break;
}

return $data;
}

$key = "abcdefg_abcdefg_abcdefg_abcdefg_";
$iv = "abcdefg_abcdefg_abcdefg_abcdefg_";

echo encryption("Hello world!", $key, $iv);
echo decryption("1ERpu8RaiyMqsXdjhCEu9IVMyQlz/A3fBR6vGbNxaVs=", $key, $iv);
[/php]