강좌
클라우드/리눅스에 관한 강좌입니다.
보안 분류

아파치웹서버보안 #3 : 아파치 설정파일 보안설정하기

작성자 정보

  • 구돌 작성
  • 작성일

컨텐츠 정보

본문

앞에서 언급한 바와 같이 아파치에서는 별도의 솔루션 없이 단지 아파치의 설정(httpd.conf)을 적당히 변경해 주는 것만으로도 적지 않은 보안위협을 제거할 수 있는 것이 사실이다.

 

. 그러나 대부분의 관리자가 설정 변경 없이 기본 설치 후 그대로 사용하거나 너무 불필요하게 많은 허용을 하여 문제가 되는 경우가 있는데, 이제부터 설명할 내용만 따라한다면 상당 부분의 보안 위협을 제거할 수 있을 것이다.

 

아파치를 컴파일 하여 설치하기 전에 몇 가지 소스 파일을 수정할 부분이 있다.

 

.

 

* httpd.h 파일 수정

다음과 같이 특정 웹 서버의 80번 포트로 telnet이나 nc로 접속을 하여 HEAD / HTTP/1.0을 실행 후 Enter를 두 번 입력하면 다음과 같이 원격지의 웹 서버 버전을 알 수 있다.

 

.

 

# telnet www.xxxx.or.kr 80

Trying 211.252.xxx.xx...

Connected to www.xxxx.or.kr.

Escape character is '^]'.

HEAD / HTTP/1.0

HTTP/1.1 200 OK

Server: Apache/1.3.27 (Unix) PHP/4.3.2

 

이는 공격자에게 불필요한 정보를 제공하여 추후 공격의 여지를 줄 수 있고, 비단 공격자에게 뿐만 아니라 자동화된 Worm 바이러스의 경우에도 웹 서버 버전을 스캔하여 버전 정보를 근거로 공격을 하는 경우도 있으므로 외부에 보이는 버전 정보를 변경하는 것이 좋다.

 

 

버전 정보는 다음과 같이

apache_1.3.xx/src/include 디렉토리에 있는 httpd.h에 설정되어 있는데, 이 값을 다음과 같이 임의의 문자열로 변경하도록 한다.

 

변경전)

#define SERVER_BASEPRODUCT “Apache”

#define SERVER_BASEREVISION “1.3.xx”

 

변경후)

#define SERVER_BASEPRODUCT “WWW-Server”

#define SERVER_BASEREVISION “2008”

 

또한 최근에는 웹 서버에 php를 함께 설치하는 경우가 자주 있는데, 이러한 경우 80번으로 telnet 접속을 하면 아래와 같이 php 버전도 보여 지게 되어 php의 취약성을 이용한 공격을 하는 경우도 있으므로 이 버전 정보를 보이지 않도록 하거나 다른 정보로 보이도록 할 수 있다.

 

.

 

HTTP/1.1 200 OK

Date: Mon, 10 May 2008 04:43:53 GMT

Server: Apache

X-Powered-By: PHP/4.x.x

Connection: close

Content-Type: text/html

 

이러한 경우 php.ini 파일에서 기본적으로 “expose_php = On“으로 설정되어 있는데, 이를 off로 변경 후 아파치를 재가동하면 "X-Powered-By: PHP/4.x.x" 부분이 보이지 않게 된다.

 

이 설정은 단순히 php 버전 정보를 보이게 할지 안 할지의 설정이며 이로 인하여 다른 기능에 영향을 주지는 않는다.

 

하지만 phpinfo()로 보았을 때는 버전 정보가 보이게 된다.

 

 

특정 서버의 헤더 정보를 살펴보려면 다음과 같은 방법을 이용하면 된다.

 

 

 

# lynx -head -source http://superuser.co.kr/

# wget -S --spider http://superuser.co.kr/

# curl --head http://www.superuser.co.kr/

 

HTTP/1.1 200 OK

Date: Mon, 25 Feb 2008 02:20:10 GMT

Server: Apache/2.0.59 (Unix) mod_ssl/2.0.59 OpenSSL/0.9.7a PHP/4.4.4

X-Powered-By: PHP/4.4.4

P3P : CP="ALL CURa ADMa DEVa TAIa OUR BUS IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC OTC"

Set-Cookie: PHPSESSID=b55b82cbf93631e47748dcac2fc80a42; path=/

Connection: close

Content-Type: text/html

 

아울러 아파치는 기본적으로 최대 256개의 httpd 프로세스를 포크(fork)할 수 있도록 설정되어 있으므로 접속자가 많은 경우 최대 프로세스수를 늘려줄 필요가 있다.

 

. 이는 httpd.confMaxClients 지시자에서 지정할 수 있는데, 만약 256 이상의 값(아래의 경우 1000)을 설정하였을 경우에는 다음과 같이 에러가 나는 것을 알 수 있다.

 

.

 

 

# httpd -t

WARNING: MaxClients of 1000 exceeds compile time limit of 256 servers,

lowering MaxClients to 256. To increase, please see the

HARD_SERVER_LIMIT define in src/include/httpd.h.

Syntax OK

 

따라서 소스에 정의되어 있는 한계 값을 변경하려면 1.3.x의 경우 아래와 같이 src/include/httpd.h 파일에서 적당한 값으로 변경해 주도록 한다.

 

, 여기에서 필요이상으로 너무 높은 값을 설정하면 불필요한 자원을 소모하게 되므로 적당한 값으로 지정하기 바란다.

 

 

변경 전)

#define HARD_SERVER_LIMIT 256

 

변경 후)

 

#define HARD_SERVER_LIMIT 1024

 

2.0.x 또는 2.2.x 에서 1.3.x 가 사용하는 prefork 방식을 사용할 경우는 소스 디렉토리 아래의 server/mpm/prefork/prefork.c 파일을 수정하면 된다.

 

 

변경 전)

#define DEFAULT_SERVER_LIMIT 256

 

변경 후)

#define DEFAULT_SERVER_LIMIT 1024

이후 설치방법은 뒤에서 설명할 보안모듈과 함께 설치하는 웹 서버 부분을 살펴보자.

 

2.2.2 아파치 보안 설정 방법

 

이제 본격적으로 아파치의 주 설정파일인 httpd.conf에서의 보안설정을 해 보도록 하자.

 

User webserv

Group webserv

 

이는 포크된 아파치 프로세스의 실행 권한을 지정한다.

 

그런데, 여기에서 아파치의 시작(starting)권한과 실행(running)권한을 혼동하지 말기 바란다.

 

아파치는 privileged 포트인 80번 포트를 바인드(bind)하고 원하는 디렉토리라면 어디든 log 파일을 남길 수 있도록 root 권한으로 시작(starting)하고 요청을 대기한다.

 

요청을 받으면 자식 프로세스(child process)를 포크(fork-복사)하여 클라이언트(방문자)의 요청을 처리하도록 한다.

 

따라서 CGISSI등 모든 요청은 이곳에서 지정된 User Group 권한으로 작동하게 된다.

 

만약 UserGroup 설정부분에 nobodywebserv가 아닌 root로 설정하였다면 웹을 통해 실행하는 cgi등도 모두 root 권한으로 작동하게 되어 심각한 보안문제가 발생하게 될 것이다.

 

.

 

만약 서버의 웹 데몬이 root 권한으로 작동하고 있고 identd 데몬이 작동하고 있다.

 

면 다음과 같이 외부에서 nmap으로 -I 질의를 하면 요청을 처리하는 프로세스의 소유자가 nobody 등 일반계정이 아닌 root임을 알 수 있다.

 

.

 

# nmap -sT -p 80 -I www.server.com

 

Interesting ports on (211.xx.xx.xx):

Port State Service Owner

80/tcp open http root

 

[아파치 실행권한 rootnobody의 관계]

 

많은 분들이 아파치 웹 서버는 nobody 또는 apache등 일반 유저로 작동하고 있다.

 

고 알고 있다.

 

. 물론 틀린 말은 아니다. 하지만 아래와 같이 실행해 보면 nobody 프로세스뿐만이 아니라 root 권한으로 작동하고 있는 다른 한 개의 프로세스가 있다.

 

는 것을 알 수 있다.

 

.

 

# ps aux|grep http

root 459 0.0 0.0 3924 684 ? S Aug23 1:10 /www/bin/httpd

nobody 478 0.0 0.1 4332 2464 ? S Aug23 1:05 /www/bin/httpd

nobody 479 0.0 0.1 4248 2624 ? S Aug23 1:06 /www/bin/httpd

 

웹 데몬이 사용하는 포트인 80번은 privileged 포트로서 오직 root만이 띄울 수 있도록 정의되어 있다.

 

. 따라서 nobody 권한으로 데몬을 실행하려면 다음과 같이 권한이 부족하다는 에러가 나며 실행할 수 없게 된다.

 

# su - nobody

$ /www/bin/httpd

fopen: Permission denied

 

만약 nobody 권한으로 데몬을 띄우려면 httpd.conf에 정의되어 있는 port 808080과 같이 1024 이후의 unprivileged 포트로 정의하여야 할 것이다.

 

. 따라서 아파치는 80번 포트를 바인드(bind)하기 위해 처음 root 권한으로 데몬을 실행하여 포트를 바인드한 이후에는 User에서 지정한 nobody등의 권한으로 http 프로세스를 포크(fork-복사)하면서 실제 클라이언트의 요청은 root가 아닌 nobody 권한으로 포크된 프로세스가 처리하도록 하는 것이다.

 

.

 

그런데, 아파치 웹 서버는 왜 nobody라는 권한으로 작동할까? 결국 nobody도 일반유저와 똑같은 권한이므로 반드시 nobody라는 계정으로 사용할 필요는 없으며 단지 관례적으로 nobody라고 사용해 왔기 때문이다.

 

. 참고로 원래 nobody는 처음 NFS에서 root를 매칭하기 위한 목적으로 사용되었었다. 그런데, 대부분의 Linux*NIX 계열의 아파치 웹 서버는 기본적으로 nobody 권한으로 작동하도록 설정되어 있다.

 

. 그러나 아파치뿐만 아니라 proftpd등을 포함한 여타의 데몬들도 실제 nobody 권한으로 작동하고 있어 만약 nobody 권한으로 작동하고 있는 특정 데몬의 취약성을 이용하여 nobody 권한을 획득하였을 경우 아파치 등 nobody로 작동하고 있던 다른 데몬의 접근권한도 얻게 될 가능성이 있다.

 

. 특히 NFS의 경우 nobody는 그 근간에 root 권한을 가지고 있으므로 결국 웹 서버 실행권한으로 nobody를 사용하는 것이 좋은 방법이 아니다. 따라서 최근 배포되는 아파치는 실행권한으로 이전의 nobody가 아닌 apache라는 계정으로 설치되는 경우도 있는데, 이름은 어떤 것이든 좋다.

 

 

결론적으로 다음과 같이 실행 쉘은 없으면서(/bin/false) nobody가 아닌 일반권한의 다른 계정을 생성하여 아파치의 실행유저로 설정하는 것이 좋다.

 

 

# useradd webserv -s /bin/false -d /www/htdocs

 

그리고 만약 위에서 생성한 webserv 계정의 암호가 설정되어 있다.

 

면 아래와 같이 실행하여 lock을 걸어두어 외부에서 로그인할 수 없도록 설정한다.

 

 

# passwd l webserv

위와 같이 실행하면 /etc/shadow 파일에서 webserv 암호 부분이 !!로 채워져 lock이 걸리게 된다.

 

원하는 대로 설정되었는지 다음과 같이 확인해 보도록 한다.

 

 

 

# grep webserv /etc/passwd /etc/shadow

/etc/passwd:webserv:x:510:510::/www/htdocs:/bin/false

/etc/shadow:webserv:!!:12298:0:99999:7:::

 

 

 

여러 유저가 함께 사용하는 호스팅 환경에서 웹서버는 nobodyapache, webserv등 특정한 유저로 실행되므로 ftp 로 파일을 업로드 할 때 소스 파일은 644와 같이 누구나 읽을 수 있는 권한으로 설정하여야 한다.

 

따라서 이러한 경우 dbconn.inc db 커넥션에 필요한 id/pw 정보가 같은 서버 내 유저에게 노출될 수 있는 보안상의 문제가 발생하게 된다.

 

이 문제를 해소하기 위해 여러 논의가 있었지만 결국 호스팅 환경에서는 해결책이 없다는 것이 결론이다.

 

.

그런데, 특정 서버의 경우 파일의 퍼미션이 700 또는 600인데 정상적으로 브라우징이 되는 경우가 있다.

 

. 이는 어떻게 가능할까?

이는 지금과 같은 보안 문제를 해결하기 위해 apache의 비공식 모듈을 이용한 경우이다.

 

.

가장 많이 알려진 것은 mod_become(http://www.snert.com/Software/mod_become/) 으로 각 가상호스트별로 트래픽을 모니터링하고 초과시 자동차단할 수 있는 mod_throttle을 개발하여 배포하는 곳이다.

 

. 아쉽지만 현재는 개발을 중단한 상태이다.

 

.

또 다른 모듈로는 mod_suid(http://freshmeat.net/projects/mod_suid/)라는 것이 있는데, 계속적으로 개발이 되고 있기는 하다.

 

두 프로그램 모두 작동원리와 설정 방법이 거의 비슷한데, 일단 아파치의 httpd.conf에서 UserGroup 모두 root 로 설정하고 각 VirtualHost마다 각 유저의 ftpid를 따로 지정해 주면 해당 도메인에 접근시 일반적인 nobody가 아닌 root 대신 지정한 유저의 id로 변경되어(become) 접근을 하는 원리이다.

 

. 현재 두 프로그램 모두 apache 1.3.x에서만 작동한다.

 

 

 

Port 80

 

앞에서 설명한대로 웹 데몬의 포트는 기본적으로 80번으로 약속되어 있으며 만약 1024 이후의 포트로 사용하려면 root 권한이 아닌 일반유저 권한으로 프로세스를 시작해도 된다.

 

포트와 관련하여 httpd.conf에는 다음과 같이 설명되어 있다.

 

.

“For ports < 1023, you will need httpd to be run as root initially.” , 1023 이하의 포트에 대해서 httpdroot로 시작하여야 한다는 뜻이다.

 

. 그 이유는 앞에서 살펴보았으므로 잘 알 것이다.

 

.

 

ErrorLog /www/logs/error_log

 

에러가 발생하였을 경우 에러뿐만이 아니라 각종 웹 서버 관련 디버깅 메시지 등이 남는데, 이러한 메시지는 문제의 원인을 찾고 문제를 해결하는데 중요한 단서가 되기도 한다.

 

에러로그는 통상적으로 logs/ 디렉토리의 error_log 파일에 남는데, 로그의 보안을 강화하기 위해 로컬이 아닌 원격지 호스트의 로그서버에 남기도록 설정할 수 있다.

 

. 이를 위해서는 ErrorLog 부분을 아래와 같이 설정하도록 한다.

 

 

ErrorLog syslog:local7

 

그리고 /etc/syslog.conf 파일에 다음과 같이 설정하면 remote_loghost라는 호스트에 에러 로그가 남게 된다.

 

물론 이때 remote_loghostip/etc/hosts에 정의되어 있어야 하거나 remote_loghost 대신 IP 주소를 직접 지정해도 된다.

 

설정을 적용하기 위해서는 syslogd 데몬을 재가동하면 된다.

 

 

 

local7.info @remote_loghost

 

access_log 관리방법

error_log와 함께 접속기록을 저장하기 위한 로그파일을 지정할 수 있는데, CustomLogLogFormat을 지정하면 다음과 같이 로그파일이 저장될 디렉토리 경로 및 파일 이름과 어떠한 형식으로 남길 것인지를 지정할 수 있다.

 

.

 

LogFormat "%h %l %u %t \"%r\" %s %b" CLF

LogFormat "%h %l %u %t \"%r\" %s %b \"%{Referer}i \"%{User-agent}i\"" ECLF

LogFormat "%t \"%404{Referer}i\" \"%r\" 404_requests

LogFormat "%!200,304,302h %u %t \"%r\" %s" Failed_requests

LogFormat "%v %{%m:%d}t %b" hosting

 

CustomLog /var/log/http/access_log CLF

CustomLog /var/log/http/404_log 404_requests

CustomLog /var/log/http/problems_log Failed_requests

 

첫 번째 LogFormatCLFdefault로 정의되어 있는 기본적인 로그 파일 저장 형식으로 /var/log/http/access_log 파일에 저장된다.

 

두 번째 줄의 ECLF는 기본형식에 %{Referer} %{User-agent}를 추가한 확장된 형식이다.

 

. 세 번째인 404_requests404 (Not Found) 에러가 난 정보만 남기도록 한 것으로 /var/log/http/404_log 파일에 저장된다.

 

 

Failed_requests는 상태코드 앞에 not의 의미인 !가 있으므로 정상적인 접속이 이루어진 상태코드인 200(OK), 304(Not Modified), 302(Moved Temporarily, Redirection)를 제외한 접속 정보만 남기도록 한 것으로 /var/log/http/problems_log 파일에 저장된다는 의미이다.

 

. HTTP1.0 상태코드에 대한 자세한 정보에 대해서는 다음의 HTTP 1.0 규약

(http://www.w3.org/Protocols/rfc2068/rfc2068)을 참고하기 바란다.

 

 

마지막의 hosting은 여러 도메인을 호스팅하는 서비스 운영 시 도메인별로 최소의 정보만을

저장하도록 하는 옵션으로 위와 같이 설정 시 "server.com 04:23 17830"과 같이 도메인(server.com) 날짜(423) 전송된바이트수(17830byte)“의 형식으로 지정된다.

 

 

 

 

[ VirutualHost에서 특정 IP를 로그에 남기지 않도록 하는 방법 ]

 

아파치 웹 로그 파일(access_log)에 특정 IP 주소의 로그를 기록하지 않도록 설정 할 수 있다.

 

. 이러한 경우 아래와 같이 SetEnvIf를 이용하여 환경 변수를 지정한 후 이 변수만 남기지 않도록 하면 된다.

 

또는 웹 로그 분석 프로그램에서 자체적으로 제공하기도 한다.

 

 

<VirtualHost 192.168.1.3>

ServerAdmin webmaster@server.com

DocumentRoot /www/htdocs/

ServerName www.server.com

LogFormat "%h %l %u %t \"%r\" %>s %b" common

ErrorLog logs/server.com-error_log

SetEnvIf Remote_Addr 192.168.100.13$ do_not_log

CustomLog logs/access_log combined env=!do_not_log

</VirtualHost>

 

 

 

로그와 관련하여 한 가지 고려해야 할 사항이 있다.

 

. 바로 로그 파일의 순환(rotation)인데, error_logaccess_log든 로그 파일을 생성하도록 설정하였을 경우 웹 서버에서는 해당 로그파일을 남기기 위해 로그파일 open -> 파일의 끝으로 이동 -> 로그 내용 추가 -> 파일 close”라는 프로세스를 거치게 되므로 로그 파일의 사이즈가 큰 경우 보안과는 별개로 속도와 성능에도 적지 않은 영향을 주게 되기 때문에 로그파일은 정기적으로 순환하여 주는 것이 좋다.

 

이를 위해서 아파치에서는 rotatelogs라는 툴을 제공하고 있는데, 이 파일은 아파치 설치 디렉토리중 bin/ 디렉토리에 있다.

 

. 아파치 디렉토리 이하의 bin/ 디렉토리에 있으면 아파치가 경로를 인식하지 못할 수 있으므로 /usr/bin/이나 /bin/ 디렉토리로 복사를 하거나 링크를 걸어두는 것이 좋다.

 

만약 rotatelogs를 별도로 지정하지 않을 경우에는 access_log라는 파일에 계속 쌓이게 되는데, 아래와 같이 rotatelogs를 설정하면 일정시간마다 access_log.xxxxxx 형식으로 잘라서 쌓이게 된다.

 

아래의 경우 86400()으로 지정하였으므로 1(24시간x60x60=86400)을 기준으로 순환하게 되는데, 만약 매시간 마다 순환하도록 남기려면 3600으로 지정하면 된다.

 

순환 주기는 로그파일의 사이즈를 보아 적당히 조정하여 지정하지 않으면 오히려 로그파일을 새로이 생성하기 위해 불필요한 부하가 유발되는 요인이 될 수 있으므로 주의하여야 한다.

 

 

 

CustomLog "|rotatelogs /var/log/httpd/access_log 86400" ECLF

 

참고로 error_log도 로테이션하도록 할 수 있는데, 다음과 같이 지정하면 된다.

 

ErrorLog "|rotatelogs /var/log/httpd/error_log 86400"

 

만약 access_log 파일의사이즈가 커져서 삭제 후 재생성하기 위해서는 데몬을 재가동해 주어야 하는 불편이 있다.

 

. 그러나 그렇게 하지 않고 파일만 0으로 만들고자 할 경우 다음과 같이 실행하면 된다.

 

 

# cat /dev/null > /var/log/httpd/access_log

 

이러한 경우 데몬을 재가동해 주지 않아도 0부터 로그 파일이 다시 쌓이게 된다.

 

 

 

Directory 설정

 

아래와 같이 기본적으로 모든 디렉토리는 웹을 통한 접근을 거부하도록 설정한 후 웹을 통한 접속을 허용할 디렉토리를 다음에 지정해 주도록 한다.

 

만약 그렇지 않고 최상위 디렉토리도 웹을 통한 접근을 허용할 경우에는 # cd /; ln -s / public_html와 같이 실행하여(물론 이는 root만이 설정 가능할 것이다.

 

.) /public_html로 링크할 경우 웹을 통해 시스템의 전체 파티션에 접근할 수 있게 된다.

 

 

<Directory />

Order Deny,Allow

Deny from all

</Directory>

 

<Directory /home*/>

Options IncludesNoExec SymLinksIfOwnerMatch ExecCGI

AllowOverride None

Order allow,deny

Allow from all

</Directory>

 

 

AllowOverride None

 

 

AllowOverride에는 All이나 None 또는 FileInfoAuthConfig등이 올 수 있는데, 이는 .htaccess 파일의 사용을 허가할 것인지 거부할 것인지에 대한 설정이다.

 

. 만약 접근 제어등을 사용하기 위해 .htaccess를 사용하려면 All, 사용하지 않는다면 None으로 지정하면 된다.

 

만약 .htaccess의 사용을 허가할 경우에는 .htaccess 파일이 위치한 디렉토리 이하의 모든 하위 디렉토리에 적용되는데, 성능(performance)의 입장에서 보았을 때는 권장하지 않는다.

 

왜냐하면 만약 클라이언트가 /home/abc/public_html/data/2/ 디렉토리에 있는 파일을 접근할 때 웹 데몬(httpd)/를 시작으로 /home/ -> /home/abc/ -->

/home/abc/public_html/ -> /home/abc/public_html/data/ ->

/home/abc/public_html/data/2/까지 이동하면서 .htaccess 파일이 있는지 여부를 매번 체크하기 때문에 불필요한 과부하를 유발할 수 있기 때문이다.

 

. 하지만 .htaccess를 이용하여 인증을 설정하는 방법이 쉽기 때문에, 필요하다면 필요한 디렉토리에만 설정하여 사용하도록 한다.

 

실제로 아래는 http 프로세스를 strace로 읽은 결과를 보여주는데, 아래와 같이 /부터 시작해서 요청한 디렉토리까지 .htaccess가 있는지 확인하는 것을 보여주고 있다.

 

.

 

open("/.htaccess", O_RDONLY) = -1 ENOENT (No such file or directory)

lstat64("/home2", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0

open("/home2/.htaccess", O_RDONLY) = -1 ENOENT (No such file or directory)

lstat64("/home2/admin", {st_mode=S_IFDIR|0701, st_size=4096, ...}) = 0

open("/home2/admin/.htaccess", O_RDONLY) = -1 ENOENT (No such file or directory)

lstat64("/home2/admin/public_html", {st_mode=S_IFDIR|0710, st_size=4096, ...}) = 0

open("/home2/admin/public_html/.htaccess", O_RDONLY) = -1 ENOENT (No such file or directory)

lstat64("/home2/admin/public_html/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0

open("/home2/admin/public_html/html/.htaccess", O_RDONLY) = -1 ENOENT (No such file or directory)

lstat64("/home2/admin/public_html/html/images", {st_mode=S_IFDIR|0755, st_size=8192, ...}) = 0

open("/home2/admin/public_html/html/images/.htaccess", O_RDONLY) = -1 ENOENT (No such file or directory)

 

 

IncludesNoExec

 

 

Includes는 서버에서 Server Side Includes(SSI)를 허용하기 위한 설정인데, shtml 코드 내에서 시스템 명령어를 실행할 수 있도록 허용하여 다른 Server Side 언어와 마찬가지로 서버에서 실행된 결과를 클라이언트에게 전송한다.

 

만약 다음과 같은 코드를 shtml내에 포함하였을 경우 /etc/passwd 파일을 보여주게 된다.

 

 

<!--#exec cmd=`cat /etc/passwd` -->

 

따라서 SSI를 허용하려면 위와 같이 IncludesNoExec로 설정하여 SSI를 허용하되 시스템 명령어를 사용할 수 없도록 하는 것이 좋으며 굳이 SSI가 필요하지 않다면 아예 이 부분을 삭제하거나 주석처리 하여 Includes를 허용하지 않도록 한다.

 

 

SymLinksIfOwnerMatch

 

보안을 위해서는 soft(심볼릭) 링크를 허용하지 않는 것이 좋다.

 

그러나 필요에 의해 굳이 soft 링크를 허용하여야 할 경우에는 파일이나 디렉토리의 소유권과 관계없이 모든 링크를 허용하는 FollowSymLinks 대신 같은 소유권인 경우에만 링크를 허용하는

SymLinksIfOwnerMatch를 사용하는 것이 좋다.

 

이를테면 다음과 같이 일반유저가 자신의 홈디렉토리에서 /etc/passwd 파일을 링크하였을 경우 FollowSymLink로 설정되었을 경우 링크한 파일에 접근이 가능하지만, SymLinksIfOwnerMatch인 경우 소유권이 다르므로 접근이 불가능하다.

 

 

 

$ ln -s /etc/passwd passwd.html

 

allowdeny

 

특정 디렉토리를 포함한 디렉토리 이하에 대한 접근통제를 하기 위해 allowdeny를 사용할 수 있는데, 이때 allowdeny의 순서를 이해하는 것이 매우 중요하다.

 

결론적으로 뒤쪽에 선언된 것이 앞쪽에 선언된 것보다 더 우선한다.

 

라고 기억하고 있으면 된다.

 

여기에서 중요한 것은 "order allow,deny" 또는 “order deny,allow"부분이며, 그 아래에 설정된 ”allow from“”deny from“중 어떤 것이 먼저 설정되어 있는가는 중요하지 않다.

 

 

따라서 위의 공식에 따라 아래의 경우에 대해 각각 살펴보자.

 

(1) Order deny,allow

allow from 192.168.1.3

deny from all

 

뒤쪽에 선언된 allow가 우선이므로 allow from에서 지정한 192.168.1.3은 허용하되 나머지(all)deny에 의해 거부된다.

 

 

(2) Order allow,deny

allow from all

deny from 192.168.1.2

 

위의 경우 deny가 우선이므로 deny from에서 지정한 192.168.1.2의 접근은 거부하되 나머지(all)allow에 의해 허용한다.

 

 

 

(3) Order allow,deny

allow from 192.168.1.3

deny from 192.168.1.2

 

deny가 우선이므로 192.168.1.2의 접근은 거부되고 192.168.1.3의 접근은 허용될 것이다.

 

. 그러나 별도로 정의되어 있지 않은 ip 이를테면 192.168.1.4의 경우에도 접근은 거부된다.

 

왜냐하면 뒤쪽에 지정된 것(deny)이 우선권이 있으면서 아울러 기본 값이기 때문이다.

 

.

 

(4) Order deny,allow

allow from 192.168.1.3

deny from 192.168.1.2

 

위와 같은 방법으로 allow가 우선이자 기본으로 작용할 것이다.

 

. 따라서 192.168.1.3192.168.1.2 이외의 ip 이를테면 192.168.1.4의 경우에는 접근이 허용될 것이다.

 

.

결론적으로 뒤쪽에 지정된 것이 기본 값이므로 from all의 개념이 되는 것이다.

 

. 그래서 뒤쪽에 allow가 있을 경우 allow from all이 되고, deny가 있을 경우 deny from all이 되는 것이다.

 

.

 

(5) Order deny,allow

allow from all

deny from all

 

위와 같은 경우는 이제 쉽게 판단할 수 있을 것이다.

 

. 뒤에 있는 allow가 우선이므로 모든 접속은 허용될 것이다.

 

. 반대로 아래의 경우에는 deny가 우선이므로 모든 접속이 거부될 것이다.

 

.

 

Order allow,deny

allow from all

deny from all

 

아울러 allow deny에서 ip를 허용하거나 또는 거부할 때 사용될 수 있는 형식은 다음과 같다.

 

- 완전한 ip (192.168.1.1)

- subnet (192.168.1. 또는 192.168.1)

- netmask (192.168.1.0/255.255.255.0)

- CIDR (192.168.1.0/24)

 

 

환경변수를 활용한 접근제어

 

이외 다양한 환경변수를 활용하여 접근제어를 할 수 있는데, 여기에서는 BrowserMatch라는 변수를 이용한 방법에 대해 알아보자. NetscapeExplorer등 웹 접속을 위해 클라이언트가 사용하는 모든 프로그램들을 유저 에이전트(User Agent)라고 하는데, 아파치에서는 BrowserMatch라는 변수를 통해 웹 접속을 시도하는 유저 에이전트(UA)를 인식할 수 있다.

 

. 바로 이러한 기능을 이용하여 검색 로봇이나 비정상적인 에이전트들을 통제할 수 있는데, 간단히 아래의 설정을 httpd.conf 파일에 추가하기만 하면 된다.

 

 

 

BrowserMatch "WebZIP" go_out

BrowserMatch "Teleport" go_out

BrowserMatch "GetRight" go_out

BrowserMatch "WebCopier" go_out

BrowserMatch "NetZip Downloader 1.0" go_out

BrowserMatch "NetZip-Downloader/1.0.62" go_out

BrowserMatch "Teleport Pro/1.29" go_out

<Directory /home/no-ua/>

Order allow,deny

Allow from all

Deny from env=go_out

</Directory>

 

위와 같이 설정할 경우에는 /home/no-ua/ 디렉토리 이하에 대해서는 go_out이라는 변수에 지정한 WebZip이나 Teleport등의 UA 프로그램이 접근할 경우 403(Forbidden) 에러가 나게 된다.

 

다른 UA도 차단하고 싶으면 위와 같이 웹 서버의 로그를 살펴보아 agent 정보에 남는 UAgo_out으로 추가해주면 되는데, 아래의 로그에서 마지막의

"Scooter/3.3"BrowserMatch에 맞는 UA가 된다.

 

 

 

192.168.34.234 - - [06/Oct/2007:09:36:58 +0900] "GET /guide/guide05_02.html HTTP/1.0" 200 16986 "-" "Scooter/3.3"

 

 

ServerSignature

 

ServerSignature404403등의 error 화면이나 mod_statusmod_info를 사용할 때 출력되는 화면에 가상 호스팅 정보와 더불어 서버 버전등을 함께 표시할 것인지에 대한 설정이다.

 

. 이 값에는 On이나 Off 또는 EMail이 될 수 있는데, EMail > On > Off 순으로 많은 정보를 제공한다.

 

이때 각각의 출력 예는 다음과 같다.

 

On의 경우: Apache/1.3.41 Server at server.com Port 80 (기본값)

 

Off의 경우 : 아무런 정보도 표시 안 됨

 

EMail의 경우 : On의 출력과 더불어 httpd.conf에서 정의된 가상호스팅의 ServerAdmin의 메일주소가 server.com에 하이퍼링크 된다.

 

 

 

굳이 에러 화면에 아파치의 버전등과 같은 서버 정보 등을 알릴 필요가 없으므로 Off로 하는 것이 좋다.

 

 

 

ServerTokens

 

이 지시자는 앞에서 설명한 바와 같이 웹 데몬 포트인 80번으로 접속 시도를 할 때 데몬의 버전정보를 보여주게 되는데, 어느 수준까지 보여줄 것인가에 대한 설정 부분이다.

 

. 기본적으로는 아래의 Full과 같이 모든 정보를 보여주도록 되어 있는데, 별도의 옵션을 지정해 주면 출력되는 정보의 수준을 조정할 수 있다.

 

. 가장 최소한의 정보만 보여주는 Prod로 설정하는 것이 좋다.

 

 

ServerTokens Prod : Server: Apache

ServerTokens Min : Server: Apache/1.3.41

ServerTokens OS : Server: Apache/1.3.41 (Unix)

ServerTokens Full : Server: Apache/1.3.41 (Unix) PHP/4.4.8 MyMod/1.2

ErrorDocument

이 지시자는 보안과 관련된 에러를 포함하여 에러가 발생하였을 경우 어떻게 처리할 것인지에 대한 설정이다.

 

. 기본 값으로는 해당하는 에러화면을 출력해준다.

 

그리고 에러화면 출력 후 보안과 관련된 에러(401:Unauthorized, 403:Forbidden)일 경우 기존의 에러코드대신 경고문이 뜨게 하거나 관리자에게 메일로 발송하도록 하는 등의 별도 조처를 취하는 것이 좋다.

 

이를테면,

 

ErrorDocument 401 http://www.police.go.kr/

 

위와 같은 경우 401 인증 에러가 날 경우 즉시 경찰청 홈페이지로 redirect하도록 하는 설정이다.

 

. 또는 다음과 같이 설정하였을 경우에는 각각의 에러 발생 시 특정 cgi가 실행되도록 설정하는 예이다.

 

.

ErrorDocument 400 /www/cgi-bin/400.cgi # Bad Request

ErrorDocument 401 /www/cgi-bin/401.cgi # Unauthorized

ErrorDocument 403 /www/cgi-bin/403.cgi # Forbidden

ErrorDocument 404 /www/cgi-bin/404.cgi # Not Found

ErrorDocument 413 /www/cgi-bin/413.cgi # Request Entity Too Large

ErrorDocument 414 /www/cgi-bin/414.cgi # Request-URI Too Large

ErrorDocument 500 /www/cgi-bin/500.cgi # Internal Server Error

아래의 소스파일은 401.cgi의 예를 보여주고 있다.

 

.

 

#!/usr/bin/perl

 

$IP = $ENV{'REMOTE_ADDR'};

$HOST = $ENV{'REMOTE_HOST'};

$REQ = $ENV{'REQUEST_METHOD'};

$HTTP = $ENV{'HTTP_HOST'};

$FILE = $ENV{'SCRIPT_NAME'};

$REQUEST = $ENV{'SCRIPT_URI'};

$AGENT = $ENV{'HTTP_USER_AGENT'};

$DATE = `date`;

$FROMEMAIL = 'webmaster@server.com';

$TO = 'webmaster@server.com';

$FROM = 'WWW-Warning';

$SUBJECT = '401 에러 경고';

$mailprog = '/usr/sbin/sendmail';

 

open (MAIL, "|$mailprog $TO") || die "Can't open $mailprog!\n";

print MAIL "From: $FROM <$FROMEMAIL>\n";

print MAIL "Subject :$SUBJECT \n";

print MAIL "content-type: text/html\n\n";

print MAIL "Web Server - $HTTP<br> ";

print MAIL "Attacking Host - $IP<br> ";

print MAIL "Request Method - $REQ <br>";

print MAIL "URL Request - $REQUEST <br>";

print MAIL "User Agent - $AGENT <br>";

print MAIL "발생시각 - $DATE <br>";

print MAIL "IP-WHOIS : <a href=http://www.dnsstuff.com/tools/whois.ch?ip=$IP>IP질의</a><br>";

close(MAIL);

 

print "Content-type: text/html\n\n";

print "<HTML><HEAD>";

print "<TITLE>401 Error</TITLE>";

print "</HEAD><BODY>";

print "<H1>Unauthorized</H1>";

print "<P>You have made an Unauthorized URL request.</P>";

print "<P>Please check the filename requested and/or the link that you followed. </P>";

print "<B>Web Site Questions:</B>";

print "<br><a href=mailto:webmaster\@server.com>webmaster\@server.com</a>";

print "<HR>";

print "</BODY>";

print "<HR>";

 

아래는 401 에러가 발생할 때 관리자에게 발송된 메일의 예와 접속자에게 보이는 페이지의 예이다.

 

.

 

Web Server - www.server.com

Attacking Host - 211.47.xx.xx

Request Method - GET

URL Request -

User Agent - Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; i-NavFourF)

발생시각 - Thu Sep 4 17:00:51 KST 2007

IP-WHOIS : IP질의

 

 

1fc6203501b3fa7ade448d379ebc6817_1655442754_6238.png
 

[그림] 접속자에게 보이는 에러 화면

 

 

LimitExcept

 

LimitExcept를 이용하여 특정 디렉토리 내에서 허용할 HTTP Method를 지정할 수 있다.

 

. 일반적으로는 HEADGET 그리고 POST만 허용하면 되므로 이외 다른 메소드는 모두 거부하도록 하는 것이 좋다.

 

이때 LimitExcept라는 지시자를 이용할 수 있는데, 아래의 경우 GETPOST를 제외한 다른 메소드는 거부하도록 하는 설정이다.

 

. 참고로 LimitExcept을 이용할 경우 HEADGET에 포함되므로 별도로 언급하지 않아도 된다.

 

 

<Directory />

<LimitExcept GET POST>

Order allow,deny

deny from all

</LimitExcept>

</Directory>

 

만약 위에서 지정한 메소드 이외의 메소드로 접속 시도할 경우에는 아래와 같이 403 에러가 나게 된다.

 

 

 

61.177.182.254 - - [09/Sep/2007:13:58:12 +0900] "CONNECT 65.54.167.230:25 HTTP/1.1" 403 -

218.38.148.231 - - [23/Sep/2007:17:03:43 +0900] "OPTIONS / HTTP/1.1" 403 -

12.44.58.108 - - [16/Sep/2007:23:39:08 +0900] "SEARCH / HTTP/1.1" 403 -

 

어떤 문서에서는 메소드(Method)를 제한 설정하기 위해 Limit를 사용한다라고 되어 있는데 이는 잘못된 설명이다.

 

. 메소드 제한은 LimitExcept를 사용하여야 하며 Limit는 허용된 메소드에 대하여 접근통제를 설정하고자 할 때 사용된다.

 

위의 방법 외에 아래에서 설명할 Rewrite Module을 이용하여 설정할 수도 있는데, 아래는 TRACE Method의 접근을 차단한 설정 예를 보여주고 있다.

 

.

 

RewriteEngine on

RewriteCond %{REQUEST_METHOD} ^TRACE

RewriteRule .* [F]

 

 

HTTP Method

 

클라이언트의 요청 중 첫 번째 줄에는 항상 메소드(Method)라는 명령어가 있는데, 메소드는 클라이언트가 어떠한 목적으로 서버에 접속을 시도하는지 알려주는 역할을 한다.

 

웹 서버에서 가장 일반적으로 사용되는 메소드는 GET,HEAD,POST이며 기타 자주 사용되지는 않지만 웹 서버에서 지원하는 메소드는 아래와 같다. 참고로 메소드는 대소문자를 구분하므로 GETget은 서로 다른 의미라는 점을 주의하기 바란다.

 

 

GET : 일반적인 웹 문서를 보기 위해 사용하는 메소드로 URI에 위치한 정보에 대한 요청이다.

 

. GET은 일반적인 문서뿐만 아니라 CGI등 폼(form)을 통해 입력된 데이터를 보내고자 할 때 사용되기도 한다.

 

그러나 GET은 데이터가 비어있으므로 보낼 자료는 아래 예와 같이 URL 뒤에 덧붙여지는데, ?를 기준으로 각각의 변수를 인식하고, 그 다음에 각각의 변수에 대한 입력 값이 전달된다.

 

이때 GET을 이용 시 URL을 통해 전송되는 사이즈의 제한이 있기 때문에 간단한 경우라면 모르겠지만 대부분 POST를 사용한다.

 

 

GET /test/get_test.cgi?name=HSB&id=antihong HTTP/1.0

HEAD : 클라이언트의 요청에 대해 서버가 응답할 때 데이터를 보내지 않고 헤더 정보만을 보낸다는 것만 제외하면 GET과 동일하다.

 

, GET은 헤더와 데이터로 함께 응답하지만 HEAD는 헤더 정보만으로 응답한다는 것이다.

 

. 따라서 웹 서버 스캐너 등에서 웹서버 버전 등을 빠르게 스캔하기 위해 자주 사용되며 검색엔진 등에서 검색DB에 저장되어 있는 특정 파일의 존재 여부나 변경 여부 등을 확인하고자 할 때 사용된다.

 

 

 

POST : CGI등을 이용하여 서버에 데이터를 업로드 하는 경우 등에 사용된다.

 

주로 서버에 글을 올리거나 파일을 업로드 할 때 사용되는데, GET과 달리 서버에 보낸 데이터는 body부분에 위치한다.

 

POST /test/post_test.cgi HTTP/1.0

name=HSB&id=antihong

 

DELETE : 서버에 있는 특정 데이터를 삭제하도록 요청한다.

 

 

 

OPTIONS : 서버로부터 옵션을 요청한다.

 

OPTIONS를 이용하면 아래와 같이 서버에서 허용되는 모든 메소드를 알 수 있다.

 

.

 

# telnet server.com 80

Trying 211.xx.xx.xx...

Connected to server.com (211.xx.xx.xx).

Escape character is '^]'.

OPTIONS / HTTP/1.0

 

HTTP/1.1 200 OK

Date: Fri, 26 Sep 2007 11:24:42 GMT

Server: Apache

Content-Length: 0

Allow: GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, PATCH, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, TRACE

Connection: close

 

TRACE : 디버깅등에 사용된다.

 

PROPFIND, PROPPATCH : DAV/WebDAV등에서 사용된다.

 

 

CONNECT : proxy 클라이언트의 요청을 허용하는데 사용된다.

 

 

 

Rewrite(재작성) rule을 이용한 침입탐지

Mod_Rewrite 모듈을 활용하면 많은 다양한 기능을 구현할 수 있는데, 특히 알파벳이나 숫자가 아닌 특수문자나 제어 문자 등 비정상적인 접속 요청을 포함할 경우 접근을 거부하도록 할 수 있다.

 

. 또는 웹에서 시스템 명령어를 실행하기 위해 /sbin이나 /bin 경로를 지정할 경우나 특정 파일에 대한 access 역시 거부하도록 할 수 있는데, 이를 잘만 활용한다면 작은 IDSIPS 역할을 하게 될 것이다.

 

. Rewrite 모듈은 기본적으로 설치되어 있지 않으므로 이 기능을 사용하려면 별도로 설치하여야 한다.

 

아래는 이와 관련하여 공격시 가장 많이 사용되는 특수 문자 등의 예를 보여주고 있다.

 

.

 

디렉토리 경로 이동 - "." ".." "..."

Unix 파이프 문자 - "|"

SemiColon - ";"

명령어 Redirection - "<" ">" ">>" "<<"

SSI 명령어 - "!"

perl 에서의 명령어 실행 - "`"

시스템 명령어 - "/bin" "/sbin" 또는 "/etc"

웹 서버 관련 파일 접근 - "httpd.conf" "access_log" "error_log"

 

아래는 위의 예를 근거로 직접 RewriteRule을 적용한 예로서 이 룰에 적용되는 접근 요청은 rewrite.log 파일에 로그가 남게 되며 [F]에 의해 403 Forbidden 에러가 나게 된다.

 

 

 

RewriteEngine On

RewriteLog /usr/local/apache/logs/rewrite.log

RewriteLogLevel 9

RewriteRule

\.\.|\.\.\.|\s|'|\"|\+|\/\/|\[|\]|\;|\~|\#|\^|\&|\(|\)|\{|\}|\;|\?|\,|<|<<||\||\!|\`|>|>>|\@|\$|\* - [F]

RewriteRule /\.[a-z0-9]|/bin|/sbin|/etc|httpd\.conf|access_log|error_log - [F]

 

먼저 RewriteEngineon 또는 off를 지정할 수 있는데, Rewrite 엔진을 활성화(on)할 것인지 또는 비활성화(off)할 것인지를 지정한다.

 

RewriteLog는 관련로그를 남기도록 하는데, 로그를 남기는 수준은 RewriteLogLevel의 영향을 받는다. RewriteLogLevel0부터 9 사이의 값을 지정할 수 있는데, 0은 로그를 남기지 않도록 하는 것이고, 9는 가능한 모든 정보를 남기도록 하는 것이므로 실제 서비스 중인 경우에는 수준을 적당히 지정하여 남기도록 하기 바란다.

 

만약 로그를 남기지 않기를 원할 경우 RewriteLog/dev/null로 하지 말고 RewriteLogLevel0으로 지정하는 것이 더욱 좋다.

 

그리고 RewriteRule에 의해 URI에 위와 같은 매칭이 되었을 경우 F 403(Forbidden) 에러가 나도록 한다는 의미이다.

 

.

 

Rewrite는 다른 용도로도 활용 가능한데, 아래의 활용 예를 보도록 하자.

 

 

 

RewriteEngine On

RewriteCond %{HTTP_REFERER} !^$

RewriteCond %{HTTP_REFERER} !^http://www.server.com/.*$ [NC]

RewriteCond %{HTTP_REFERER} !^http://www.server.com:80/.*$ [NC]

RewriteCond %{HTTP_REFERER} !^http://server.com/.*$ [NC]

RewriteCond %{HTTP_REFERER} !^http://server.com:80/.*$ [NC]

RewriteCond %{HTTP_REFERER} !^http://192.168.1.10/.*$ [NC]

RewriteCond %{HTTP_REFERER} !^http://192.168.1.10:80/.*$ [NC]

ReWriteRule .*\.(jpg|gif)$ http://www.police.go.kr/ [R,L]

 

위 예의 경우는 외부에 있는 임의의 사이트에서 다른 서버에 있는 컨텐츠를 마치 자신이 제공하는 것처럼 위조하는 일종의 ‘image theft’를 차단하는 설정 예를 보여주고 있는데, RewriteCond를 이용하여 HTTP_REFERER이 자신의 도메인인 server.com이나 www.server.com 또는 192.168.1.10이 아닌 요청은 대신 http://www.police.go.kr/ 사이트로 Redirect하도록 하는 설정이다.

 

. , server.comHTTP_REFERER에서 지정한 이외의 사이트에서 그림파일을 링크하였을 경우에는 그림 파일을 브라우징하지 않고 대신 경찰청 홈페이지가 뜨도록 하는 것이다.

 

. 여기에서 [NC]nocase의 의미로 대소문자를 구분하지 않는다는 의미이다.

 

. , HTTP_REFERERhttp://server.com/이나 http://SerVer.Com/이든 신경 쓰지 않고 같은 것으로 해석한다는 의미이다.

 

. 그리고 마지막의 [R,L]에서 RRedirect 의 의미로 강제적으로 다음에서 지정한 URL로 포워딩 한다는 의미이며 LLast의 의미로 마지막 룰이라는 뜻이다.

 

.

 

좀 더 확장한다면 다소 복잡하지만 다음과 같은 기능도 사용 가능하다.

 

 

RewriteEngine On

RewriteCond %{REMOTE_ADDR} "^63\.148\.99\.2(2[4-9]|[3-4][0-9]|5[0-5])$" [OR]

RewriteCond %{REMOTE_ADDR} ^12\.148\.196\.(12[8-9]|1[3-9][0-9]|2[0-4][0-9]|25[0-5])$ [OR] # NameProtect spybot

RewriteCond %{REMOTE_ADDR} ^64\.140\.49\.6([6-9])$ [OR] # Turnitin spybot

RewriteCond %{HTTP_REFERER} iaea\.org [OR] # spambot

RewriteCond %{HTTP_USER_AGENT} e?mail.?(collector|magnet|reaper|siphon|sweeper|harvest|collect|wolf) [NC,OR] # spambot

RewriteCond %{HTTP_USER_AGENT} express [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} extractor [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} flashget [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} FrontPage [OR] # stupid user trying to edit my site

RewriteCond %{HTTP_USER_AGENT} getright [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} go.?zilla [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} grabber [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} imagefetch [OR] # rude bot

RewriteCond %{HTTP_USER_AGENT} httrack [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} "Microsoft URL Control" [OR] # spambot

RewriteCond %{HTTP_USER_AGENT} "mister pix" [NC,OR] # rude bot

RewriteCond %{HTTP_USER_AGENT} MSIECrawler [OR] # IE’s "make available offline" mode

RewriteCond %{HTTP_USER_AGENT} net.?(ants|mechanic|spider|vampire|zip) [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} nicerspro [NC,OR] # spambot

RewriteCond %{HTTP_USER_AGENT} ninja [NC,OR] # Download Ninja OD

RewriteCond %{HTTP_USER_AGENT} tele(port|soft) [NC,OR] # OD

RewriteCond %{HTTP_USER_AGENT} web.?(auto|bandit|collector|copier|downloader|mirror|site|snake|zip) [NC,OR] # ODs

RewriteCond %{HTTP_USER_AGENT} vayala [OR] # dumb bot, doesn’t know how to follow links

RewriteRule .* - [F,L]

 

위는 가장 대표적인 스팸봇이나 스파이봇 등의 로봇 프로그램이 접근하는 것을 차단하는 예를 보여주고 있는데, 먼저 REMOTE_ADDR 환경변수를 이용하여 접속을 요청하는 ip가 위에서 지정된 ip(특정 스파이봇이 사용하는 전용 ip)인 경우 차단하는 예를 보여주고 있고, HTTP_USER_AGENT를 이용하여 로봇 등의 특정 Agent를 차단하는 예를 보여주고 있는데, 정상적인 경우 이를테면 MicroSoft Internet Explorer 5.5의 경우 HTTP_USER_AGENT MSIE 5.5로 저장된다.

 

 

좀 더 확장하여 만약 경쟁업체 등 특정 사이트에서 클릭하여 우리가 관리하는 사이트로 오는 접속을 모두 다른 도메인으로 Redirect 하고자 한다면 또는 아예 접속을 차단하고자 한다면 어떻게 하여야 할까? 간단히 아래와 같은 룰을 설정하면 될 것이다.

 

.

 

RewriteCond %{HTTP_REFERER} ^http://www.badserver.com/.*$ [NC]

ReWriteRule .* http://www.police.go.kr/ [R,L]

 

위의 예는 HTTP_REFERERhttp://www.badserver.com/

, http://www.badserver.com/ URL을 포함한 사이트에서 링크를 클릭하여 우리 사이트로 오는 접속인 경우 http://www.police.go.kr/ 사이트로 R(Redirect)하고 L(Last)로 끝내는 것을 알 수 있다.

 

. 만약 접속 자체를 차단하려면 ForbiddenF를 지정하면 될 것이다.

 

. 아파치의 RewriteRule에 대한 보다 자세한 내용은 아래의 URL을 참고하기 바라며 최근에는 보안 전용모듈인 modsecurity를 사용하는 것이 더 편리하다.

 

 

 

http://www.superuser.co.kr/apache/apache2_manual/misc/rewriteguide.html

 

port 80 공격의 흔적들

 

최근에는 웹 해킹이라는 용어가 자주 사용될 정도로 웹을 통한 공격이 심심치 않게 확인되고 있다.

 

. 이러한 공격의 형태는 주로 취약한 웹 프로그램의 버그를 악용하여 사용되는데, 웹 서버의 로그를 보면 이러한 공격을 파악할 수 있다.

 

. 아래에서 대표적인 몇 가지 예를 살펴보도록 하자.

 

 

 

* “..” ..는 주로 상위나 특정 디렉토리로 이동할 때 웹 서버의 로그에 보이는 경우인데, 전자상거래등 특정 프로그램에서 가끔 보이는 경우도 있지만 일반적인 경우 기록되지 않는다.

 

또한 취약한 cgi 프로그램의 취약성을 이용하여 서버의 각종 환경 설정 파일을 살펴보는 경우도 많다. 일반적으로 사용자 id 및 홈디렉토리 등의 정보가 저장된 /etc/passwd 뿐만 아니라 암호화된 암호가 저장된 /etc/shadow 각종 네트워크 정보가 저장된 /etc/hosts, 데몬등의 정보가 저장된 /etc/xinetd.conf, 웹 서버의 설정 파일인 httpd.conf 등이 대표적이다.

 

.

 

) http://host/vuln.cgi?file=../../../../../etc/passwd

 

* "|"

|파이프라고 하는데, UNIX 명령어중 특정한 명령어의 출력을 입력으로 받고자 할 때 사용된다.

 

일반적인 URL에서는 사용되지 않는 문자이므로 정상적인 경우에는 보이는 경우가 없을 것이다.

 

.

 

) http://host/vuln.cgi?cmd=/bin/ls|grep abc

 

* ";";는 여러 명령어를 한 줄에 실행하고자 할 때 사용된다.

 

이를테면 공격코드에서 많이 보이는 경우는 아래와 같은 것으로 먼저 id 명령어로 현재의 권한을 확인 후 시스템의 이름 및 버전을 확인하도록 한다.

 

정상 프로그램에서도 보이는 경우가 가끔 있다.

 

.

 

) http://host/vuln.cgi?cmd=id;uname

 

* "`"

``perl등에서 시스템 명령어를 실행하고자 할 때 사용되는데, URL내에 이러한 특수문자가 포함되어 있다.

 

면 공격을 의심하여야 할 것이다.

 

. 아래는 perl 내에서 ps를 실행한 경우이다.

 

.

 

) http://host/vuln.cgi?=`ps`

* "/bin/ls"

잘 아시다시피 ls는 특정 디렉토리 이하에 있는 파일의 목록을 보고자 할 때 사용되는 명령어인데, 정상적인 URL에서는 보일 수 없으므로 로그에 이 문자가 기록되어 있다.

 

면 공격을 의심해 보아야 한다.

 

이외에도 /bin/cat, /bin/rm, /bin/wget, /bin/id, /bin/ps, /bin/echo 역시 웹 해킹에서 자주 사용되는 명령어들이다.

 

.

) http://host/vuln.cgi?cmd=/bin/ls

 

 

 

웹 접근통제 관련

앞에서 잠깐 allowdeny를 살펴보았지만 좀 더 확장하여 아파치에서 제공하는 접근제어 기능을 이용하면 특정 디렉토리 또는 특정 파일에 대하여 특정 ip 사용자 또는 특정 아이디/암호를 통하여 인증된 사용자만 접근이 가능하도록 설정할 수 있다.

 

. 이는 주 설정파일인 httpd.conf에서 설정하는 방법과 인증을 설정할 디렉토리에서 .htaccess 파일을 이용하여 설정하는 방법이 있는데, 형식은 동일하다.

 

먼저 주 설정 파일인 httpd.conf를 이용하는 방법을 알아보자.

 

<Directory /home/secret/public_html>

Order deny, allow

Deny from all

Allow from 192.168.1.

</Directory>

 

httpd.conf파일에 위와 같이 설정한 후 웹 데몬을 재실행(killall -HUP httpd)하면 /home/secret/public_html 디렉토리 이하에서는 192.168.1. 대역에서만 접근을 허용하게 된다.

 

따라서 /home/secret/public_html내의 하위 디렉토리 구조 및 파일 이름을 알고 있어도 지정된 ip 대역에서만 접근이 가능하므로 효율적으로 접근 통제를 할 수 있게 된다.

 

 

다음으로 .htaccess를 이용하는 방법에 대해 알아보자. 먼저 접근 통제할 디렉토리, 이를테면 /home/secret/public_html 디렉토리에 아래와 같은 내용으로 .htaccess 파일을 생성해 두면 192.168.1.4192.168.1.6 ip에서만 접근이 가능하며 이외의 ip에서는 접근이 거부된다.

 

이 설정은 .htaccess 파일이 있는 현재의 디렉토리 이하의 모든 디렉토리에도 적용되므로 주의하여야 한다.

 

 

 

<Limit GET POST>

order deny,allow

allow from 192.168.1.4

allow from 192.168.1.6

deny from all

</Limit>

 

[ IP대역 지정방법 ]

 

.htaccess 등에서 ip 대역을 지정하는 방법은 다음과 같이 여러 방법으로 가능한데, 각자의 상황에 맞게 적당한 방법을 이용하면 될 것이다.

 

. 만약 마지막의 permitted.server.com 과 같이 호스트이름을 지정할 경우 역 질의(reverse lookup) 결과가 지정된 호스트 이름이어야 한다.

 

(, nslookup 192.168.1.3으로 질의하였을 경우 permitted.server.com로 결과값이 나와야 한다.

 

따라서 이 방법은 그리 많이 사용되지는 않는다.

 

)

 

- 192.168.1.3 : 특정 ip를 지정

- 192.168.1.0/255.255.255.0 : netmask를 이용하여 지정

- 192.168.1.0/24 : CIDR를 이용하여 지정

- 192.168.1. 또는 192.168.1.* : ip 대역을 지정

- permitted.server.com : 특정 호스트를 지정

 

ip 대역을 통한 인증 외에 아이디/암호를 통한 인증기능도 이용할 수 있는데, 설정 예는 다음과 같다.

AuthName "Staffs Only."

AuthType Basic

AuthUserFile /usr/local/secret-data/.htpasswd

AuthGroupFile /dev/null

ErrorDocument 401 " ABC 직원만 접근 가능합니다.

 

<Limit GET POST>

require valid-user

Satisfy all

order deny,allow

allow from 192.168.1.4

allow from 192.168.1.6

deny from all

</Limit>

 

위의 예는 아이디/암호를 통한 인증과 ip 대역을 통한 인증을 함께 이용한 것으로 Satisfy all로 지정하였을 경우 두 조건(ip대역 및 아이디/암호 인증) 모두 만족하여야 한다.

 

만약 어느 한 조건만 충족되면 접근을 허용하고자 할 경우에는 Satisfy all 대신 Satisfy any를 쓰면 된다.

 

satisfyall이 기본 값이므로 아무런 설정이 없을 경우에는 all로 적용된다.

 

그리고 AuthUserFile은 인증을 허용할 아이디/암호 정보가 저장되어 있는 파일의 위치를 지정하는 것인데, 이러한 용도의 파일로서는 통상적으로 .htpasswd를 사용한다.

 

.htpasswd 파일을 생성하는 명령어 형식은 다음과 같다.

 

# htpasswd -c /usr/local/secret-data/.htpasswd test

New password: xxxx

Re-type new password: xxxx

Adding password for user test

 

만약 .htpasswd 파일의 경로가 /으로 시작하면 최상위 디렉토리인 /부터 시작되는 절대경로를 뜻하며, 별도지정이 없을 경우에는 현재 디렉토리를 의미한다.

 

특히 .htpasswd는 웹으로 직접 접근하여 볼 수 없도록 html 문서 트리(DocumentRoot) 이하에 두지 말고 웹에서 접근이 불가능한 시스템 내 별도의 디렉토리를 지정하는 것이 좋다.

 

여기에서 -ccreate의 의미로 .htpasswd 파일을 처음 생성할 때 사용하는 옵션이다.

 

. 이 옵션을 사용하면 지정한 파일을 초기화하므로 기존에 이미 존재하는 파일에 추가하고자 할 경우에는 -c를 빼고 설정하여야 한다.

 

 

 

# htpasswd -p .htpasswd test1

Warning: storing passwords as plain text might just not work on this platform.

New password:xxxx

Re-type new password:xxxx

Updating password for user test1

 

여기에서 -p의 의미는 plaintext의 의미로 암호 저장 시 평문형태로 저장되도록 하는 것이다.

 

. 이때 생성된 .htpasswd 파일을 보면 다음과 같이 -p 옵션을 사용하지 않고 생성한 test 계정의 암호는 암호화되었지만, -p 옵션을 사용한 test1의 암호는 평문으로 저장되어 있는 것을 알 수 있다.

 

.

 

test:4PVNjMd5nLGU2

test1:1234

 

이처럼 .htaccess를 이용하면 쉽게 인증을 구현할 수 있지만 “.htaccess를 사용할 때 주의할 점이 몇 가지 있다.

 

.

 

.htaccess를 사용할 때 주의할 점

 

.htaccess를 사용할 때 문법에 어긋날 경우, 해당 디렉토리에 접근하면 500 Internal Server Error가 난다. 따라서 특별한 이유 없이 500 Error가 날 경우에는 .htaccess가 제대로 설정되어 있는지 확인하도록 한다.

 

 

 

Windows에서 메모장을 이용하여 .htaccess를 생성할 경우 파일을 저장하면 .htaccess.txt등으로 저장되는 경우가 있는데, 물론 이 파일을 업로드해도 접근제어는 작동하지 않게 된다.

 

서버에 업로드한 후에는 확장자가 정확하게 되어 있는지 확인하도록 한다.

 

 

 

.htaccess의 모든 기능을 이용하려면 httpd.confAllowOverride All로 지정되어야 하는데, 이 설정이 되어 있는 상태에서 만약 /www/htdocs/data/support/index.html의 파일을 접근하게 되면 웹 서버에서는 다음과 같이 최상위 디렉토리에서부터 순차적으로 내려가면서 .htaccess의 파일 존재 유무를 찾게 된다.

 

 

 

/

/www/

/www/htdocs/

/www/htdocs/data/

/www/htdocs/data/support/

 

따라서 .htaccess를 사용하면 편리하게 인증 기능을 사용할 수 있지만 반면 일정 정도 웹 서버 성능에 악영향을 주게 된다.

 

물론 이로 인한 영향은 이론적인 면에서의 언급이며 실제로는 거의 느끼지 못할 정도이다.

 

. 따라서 다음과 같이 전체적으로는 AllowOverride None 으로 지정하되 .htaccess를 사용할 디렉토리에만 AllowOverride All로 지정한다면 이로 인한 성능의 저하를 최소화할 수 있게 된다.

 

 

 

<Directory />

AllowOverride None

</Directory>

 

<Directory /www/htdocs/data/support>

AllowOverride All

</Directory>

 

만약, .htaccess 인증 기능을 사용하지 않으려면 아예 AllowOverride None으로 지정하도록 한다.

 

.htaccess.htpasswd에서는 인증과 관련된 설정 정보와 아이디/암호 등이 저장되어 있다.

 

. 따라서 웹에서 이 파일에 대한 직접적인 접근을 거부하도록 설정하여야 하는데, 이를 위해서는 아래와 같이 .ht로 시작하는 파일에 대해서는 접근을 거부하도록 설정하면 된다.

 

물론 이는 아파치에서 기본적으로 설정되어 있다.

 

.

 

<Files ~ "^\.ht">

Order allow,deny

Deny from all

</Files>

 

아이디/암호 인증을 위해 htpasswd를 사용할 경우 Base64로 인코딩하기 때문에 어렵지 않게 원래의 암호로 디코딩 할 수 있다.

 

. 따라서 암호화된 암호라 하더라도 쉽게 암호를 풀 수 있으므로 htpasswd 대신 좀 더 진보된 방식인 md5 hash 방식의 다이제스트 인증(htdigest)을 사용할 수 있는데, 사용형식은 다음과 같다.

 

# htdigest [-c] passwordfile realm username

 

여기에서 htdigest는 기본 인증방식인 htpasswd보다 안전하기는 하지만 지원되지 않는 브라우저가 있으므로 실제 서비스 할 때에는 주의하여야 한다.

 

아래는 기본 인증방식 대신 다이제스트 인증방식을 위해 설정하는 예를 보여주고 있다.

 

.

<Directory /www/htdocs/protected>

AuthType Digest

AuthName "Private Access"

AuthDigestFile /var/local/.htpasswd

Require user test

</Directory>

 

# htdigest -c /var/local/.htpasswd realm test

New password: xxxx

Re-type new password: xxxx

Adding password for user test

 

파일 업로드가 가능한 게시판이나 자료실을 통해 특정 파일 이를테면 txt 확장자를 가진 파일을 php로 실행가능 하도록 하는 .htaccess 를 업로드한 후 확장자는 txt이지만 실제로는 php인 각종 웹쉘 또는 스팸발송파일을 업로드 하는 경우가 많다. 이러한 경우 쉽게 웹해킹이 가능하므로 가급적 파일 업로드를 제한하되 꼭 하여야 한다면 확장자등에 대한 체크를 엄격하게 하여야 한다.

 

txt php로 인식 가능하도록 하는 .htaccess의 예는 다음과 같다.

AddType application/x-httpd-php .php .php3 .php4 .htm .html .txt

필자의 경우 이러한 .htaccess는 다음의 명령어를 실행하여 확인한다.

 

# find `locate .htaccess` -exec grep 'txt' {} /dev/null \;

 

또한 404등의 에러가 발생 시 공격자가 의도한 특정 사이트로 redirect하도록 하는 .htaccess를 업로드 하는 경우도 있다.

 

. 이러한 .htaccess는 다음의 명령어를 실행하여 확인한다.

 

# find `locate .htaccess` -exec grep 'http://' {} /dev/null \;

 

만약 위와 같은 .htaccess를 확인하였을 경우에는

- 해당 .htaccess 파일을 삭제하고

- 파일의 소유권을 확인하여, nobody등의 웹서버 실행권한일 경우 웹취약성 여부를 확인 하며

- 파일 생성 날짜를 확인, 해당 날짜의 웹로그 등을 확인하여 어떤 방법으로 공격하였는지 확인하도록 한다.

 

- 아울러 해당 디렉토리에 대해 AllowOverride None을 설정하여 .htaccess 사용을 해제하도록 한다.

 

이를테면 특정 업로드 디렉토리 이하에서 .htaccess의 기능을 해제하려면 httpd.conf에 다음과 같은 설정을 추가해 주면 된다.

 

 

<Directory "/home*/*/public_html/tt/board/db/board/*/upload">

AllowOverride None

</Directory>

<Directory "/home*/*/public_html/*/data">

AllowOverride None

</Directory>

 

물론 이보다는 전체적으로 .htaccess 사용을 제한하되 특정 디렉토리에서만 .htaccess 사용을 허가하는 방식이 더 권장된다.

 

 

특정 파일에 대해 인증 설정방법

 

특정 디렉토리 이하에 대해서 접근을 제어하고자 할 때에는 .htaccess를 사용하거나 httpd.conf에서 <Directory>를 이용하여 제어를 할 수 있다.

 

는 것을 알았다. 그런데, 만약 특정한 파일에 대해서만 접근을 제한하고자 한다면 어떻게 해야 할까? 이때에는 Location이나 Files 지시자를 사용하면 된다.

 

httpd.conf 파일에 아래와 같이 설정할 경우 모든 디렉토리 이하의 secret.html 파일에 대해서는 192.168.1.1에서만 접근이 가능하게 된다.

 

 

<Location /secret.html>

order deny,allow

deny from all

allow from 192.168.1.1

</Location>

 

만약 여러 도메인이 설치되어 있는 호스팅 서버의 경우 secret.server.com 도메인 내 secret.html에 대해서만 접근을 제어하고자 할 경우에는 아래와 같이 VirtualHost 설정 내에서 하면 된다.

 

 

 

<VirtualHost secret.server.com>

ServerAdmin secret@server.com

DocumentRoot /usr/local/apache/htdocs/secret/

ServerName secret.server.com

<Location /secret.html>

order deny,allow

deny from all

allow from 192.168.1.1

</Location>

</VirtualHost>

 

또는 위와 같이 ip가 아니라 특정한 아이디/암호를 입력한 유저에 대해서만 특정 파일에 대하여 접근을 허용하고자 할 때가 있다.

 

. 이러한 경우에는 httpd.conf에 아래와 같이 설정하면 된다.

 

 

 

<VirtualHost secret.server.com>

ServerAdmin secret@server.com

DocumentRoot /usr/local/apache/htdocs/secret/

ServerName secret.server.com

<Files secret.html>

AuthName "ID/PW 를 입력하세요."

AuthType Basic

AuthUserFile /usr/local/secret-data/.htpasswd

Require valid-user

</Files>

</VirtualHost>

 

 

CoreDumpDirectory

 

CoreDumpDirectory는 디버깅(debugging)을 하고자 할 때 지정하는데, 기본적으로 ServerRoot 변수에서 지정한 디렉토리에 저장된다.

 

그런데 이 파일에는 서버의 메모리 환경 변수나 암호등과 같이 민감한 정보가 저장될 수 있으므로 다음과 같이 남기지 않도록 설정하는 것이 좋다.

 

CoreDumpDirectory /dev/null

 

 

FollowSymLinksSymLinksIfOwnerMatch

 

FollowSymLinksSymLinksIfOwnerMatch는 둘 다 링크를 허용하는 지시자이지만 SymLinksIfOwnerMatch는 링크를 하는 소유자(owner)가 링크를 거는 목적지의 파일이나 디렉토리 소유자(owner)와 동일한 경우에만 허용하는 지시자이다.

 

. 따라서 FollowSymLinks 가 정의된 서버에서 일반 유저가 자신의 디렉토리에서 ln -s /etc/passwd passwd.txt와 같이 실행 후 웹에서 http://server.com/~user/passwd.txt를 접속하면 /etc/passwd 파일을 접근할 수 있게 되지만, SymLinksIfOwnerMatch로 지정되어 있으면 일반유저는 자신의 uid와 동일한 파일이나 디렉토리에만 접근을 하게 될 뿐 다른 파일에는 접근이 불가능하게 되므로 링크를 설정한다 하더라도 root 소유의 /etc/passwd 파일에는 접근할 수 없게 된다.

 

따라서 보안적인 측면에서는 SymLinksIfOwnerMatchFollowSymLinks보다는 권장되지만 심볼릭 링크를 검사하기위해 시스템호출을 한 번 더 해야 하므로 성능상의 관점에서는 FollowSymLinks를 권장한다.

 

 

 

유저이름, 홈 디렉토리 경로, uid 등 민감한 정보가 정의되어 있는 /etc/passwd 파일은 기본적으로 644로 되어 있어 누구나 읽을 수 있게 되어 있다.

 

. 따라서 이를 640등과 같이 외부에서 읽을 수 없도록 설정하면 해결되지 않을까 생각할 수 있다.

 

. 그러나 이렇게 될 경우 당장 여러 문제가 발생하게 되는데, telnet 접속을 할 경우 아래와 같이 uid 502에 대한 유저이름을 알 수 없어 프롬프트가 아래와 같이 보이게 된다.

 

id: cannot find name for user ID 502

id: cannot find name for user ID 502

[I have no name!@server user1]$

 

그리고 nobody 권한으로 작동하는 ftp 접속도 불가능한데, 이때는 아래와 같이 /etc/passwd 파일에 대한 읽기권한이 없어 에러가 난 것을 알 수 있다.

 

.

Sep 30 17:58:52 server proftpd[19168]: ftp.server.com (192.168.10.3[192.168.10.3]) - FTP session opened.

Sep 30 17:58:53 server proftpd[19168]: ftp.server.com (192.168.10.3[192.168.10.3]) - Unable to open password file /etc/passwd for reading: Permission denied

Sep 30 17:58:54 server proftpd[19168]: ftp.server.com (192.168.10.3[192.168.10.3]) - no such user 'user1'

 

 

Indexes

 

Indexes는 특정 디렉토리 이하에 index.html과 같이 DirectoryIndex에서 지정한 파일이 없을 경우 해당 디렉토리에 존재하는 하위 디렉토리 및 파일의 목록을 그대로 보여주도록 하는 설정이다.

 

. 이러한 경우 불필요하게 중요한 정보를 제공하게 되므로 이 옵션은 반드시 사용하지 않도록 하여야 한다.

 

사용하지 않으려면 간단히 Indexes라는 부분을 삭제하면 된다.

 

만약 현재 옵션에서 indexes 옵션을 사용하려면 +indexes, 사용하지 않으려면 -indexes를 지정하면 된다.

 

그리고 자신의 특정 디렉토리 이하에서만 사용하고자 한다면 httpd.conf에서 indexes를 허용하지 말고, 해당 디렉토리에서 .htaccess를 이용하여 설정하도록 한다.

 

거듭 강조하지만 이 부분은 많이 실수하는 부분이므로 주의하여야 한다.

 

 

UserDir

 

UserDirhttp://server.com/~id/ 형태의 홈페이지를 제공하기 위한 용도로 사용되는데, 기본적으로 UserDirpublic_html이 사용된다.

 

따라서 http://server.com/~user1/로 접속을 한다면 /home/user1/public_html/ 디렉토리에 있는 index.html 파일을 브라우징할 것이다.

 

. 그러나 만약 다음과 같은 경우를 살펴보자.

 

root 계정의 홈디렉토리가 /이고, UserDir ./로 설정되어 있고, Indexes가 설정되어 있을 경우 http://www.server.com/~root/로 접속하면 다음과 같이 최상위 시스템 이하의 모든 디렉토리 구조가 보이게 될 것이다.

 

.

 

1fc6203501b3fa7ade448d379ebc6817_1655442801_661.png
 

[그림] ~root/ 로 접속시 화면

 

따라서 http://hostname/~id/ 형태로 서비스를 하지 않는다면 가급적 아래와 같이 UserDir을 사용하지 않는 것이 좋다.

 

 

 

<IfModule mod_userdir.c>

UserDir disabled

</IfModule>

 

만약 사용하여야 한다면 다음과 같이 root 계정에 대해서는 서비스를 disable하기 바란다.

 

아래의 설정은 모든 계정에 대해서는 UserDir을 제공하지만 root에 대해서는 제공하지 않는다는 의미이다.

 

.

 

<IfModule mod_userdir.c>

UserDir public_html

UserDir enabled *

UserDir disabled root

</IfModule>

 

만약 특정한 유저에 대해서만 UserDir을 제공하고자 한다면 반대로 기본적으로 허용하지 않고 허용하여야 할 계정에 대해서만 아래와 같이 별도로 지정해 주면 된다.

 

 

 

<IfModule mod_userdir.c>

UserDir public_html

UserDir disabled

UserDir enabled user1, user2

</IfModule>

 

또한 UserDir을 허용할 경우 다음과 같은 취약성을 이용한 문제가 발생할 수도 있다.

 

.

아래 공격의 예를 보도록 하자.

 

 

 

# ./m00-apache-w00t -t test.server.com -u test_userlist

 

[*] Apache 1.3.*-2.0.48 remote users disclosure exploit by m00 Security.

 

[*] Checking http server [test.server.com:80]...

Apache => no

Vulnerable => yes

OS =>

[*] Searching for system accounts...

sergey =>

sasha =>

pavel =>

dark =>

hacker =>

haxor =>

user =>

admin =>

support =>

test => yes

lamah =>

lame =>

lamo =>

bill => yes

john =>

pasha =>

[*] Searching complete.

30 users checked

2 users found

 

, 위와 같이 실행하면 서버에 어떤 계정이 있는지를 알 수 있는데, 이는 아래의 로그를 보는 바와 같이 간단하게 http://IP/~username/을 접속하여 서버에서 403인지 아니면 404로 응답하는지를 확인하면 된다.

 

위 프로그램을 이용한 자동화된 공격 프로그램도 공개되어 있는데, 이 프로그램에서는 메시지가 403의 경우 서버에 존재하는 계정이므로 이 계정에 대해서는 아이디와 동일한 암호 또는 1234등 간단한 암호에 대해 telnet 로그인 시도를 한 후 로그인이 성공하면 각종 공격 코드를 실행하여 root 권한 획득을 시도하게 된다.

 

 

192.168.3.4 - -[17/Dec/2007:08:38:25 +0900] "HEAD /~sergey HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~sasha HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~pavel HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~dark HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~fucker HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~satan HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~evil HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~poison HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~god HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~guest HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~dima HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~ftp HTTP/1.1" 404 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~test HTTP/1.1" 403 0

192.168.3.4 - - [17/Dec/2007:08:38:25 +0900] "HEAD /~lamah HTTP/1.1" 404 0

 

 

* 기타 서비스거부(DoS) 방어 설정

 

Timeout

 

Timeout 지시자는 클라이언트의 요청에 의해 서버와 연결이 된 후 클라이언트와 서버 간에 아무런 메시지가 발생하지 않을 동안의 대기 시간으로서 기본 값으로는 300(s) , 5분으로 되어 있는데, 이 값을 60정도 또는 그 이하 값으로 수정하는 것이 좋다.

 

여기에서 Timeout 의 의미는 용량이 큰 데이터를 수신하는 동안의 시간을 의미하는 것이 아니라 서버에 접속을 맺은 채 아무런 데이터 교환이 없을 때의 시간이므로 필요이상으로 크게 설정할 필요가 없다는 점을 유의하기 바란다.

 

일부 서비스거부 공격 중에는 일단 접속을 맺은 후 아무런 데이터를 교환하지 않은 채 접속만 유지함으로써 더 이상의 서비스를 진행하지 못하도록 하는 형태도 있기 때문이다.

 

.

RLimitMEM

 

http 프로세스를 통해 시스템의 부하가 갑자기 상승하는 경우가 있다.

 

. 이는 의도적이든 그렇지 않든 특정 cgi php등의 프로그램이 과도한 메모리를 소모하기 때문인데, 실제로 시스템의 메모리를 과도하게 소모하는 프로그램을 짜는 것은 간단히 단 줄의 코드로도 가능하다.

 

아래와 같이 메모리를 소모하는 간단한 C 코드를 작성해 보자. 참고로 스크립트 키드의 악용을 막기 위해 코드 일부를 변경하였다.

 

/* memory.c */

 

#include <unistd.h>

#include <stdlib.h>

 

void html_content(void);

 

int main() {

char *some_memory;

int size_to_allocate = ONE_K;

int megs_obtained = 0;

int ks_obtained = 0;

 

html_content();

printf("Program Executed !!!<p>");

 

while (1) {

for (ks_obtained = 0; ks_obtained < 1024; ks_obtained++) {

some_memory = (char *)malloc(size_to_allocate);

if (some_memory == NULL) exit(EXIT_FAILURE);

sprintf(some_memory, "Hello World");

}

printf("Now allocated %d Megabytes<br>\n", megs_obtained);

}

exit(0);

}

 

void html_content(void)

{

printf("Content-type: text/html\n\n");

}

 

????[유용한 팁]

특정 프로그램이 사용하는 메모리의 양은 어떻게 알 수 있을까? 아래와 같이 top을 실행한 후 RSS 부분을 보면 해당 프로세스가 사용 중인 메모리를 알 수 있는데, 아래의 경우 snort 프로세스가 15M를 사용 중이라는 것을 알 수 있다.

 

. 그리고 top을 실행할 때 -d 1 옵션을 주면 매 1초마다의 변동 상황을 알 수 있고 스페이스바를 입력할 때마다 변동 상황을 실시간으로 확인가능하다.

 

또한 top이 작동하는 중에 M을 입력하면 아래 그림과 같이 메모리를 많이 사용하는 프로세스별로 분류하여 보여준다.

 

만약 CPU 사용률을 기준으로 보려면 P, 가장 최근의 프로세스를 기준으로 보려면 A, 실행시간이 긴 프로세스를 기준으로 보려면 T를 입력하면 된다.

 

1fc6203501b3fa7ade448d379ebc6817_1655442859_7116.png
 

[그림] top 실행 예

 

소스를 컴파일 파일을 http://server.com/memory.cgi 같이 웹에서 실행해 보도록 하자.

 

메모리와 CPU의 사양이 충분한 대형 시스템이라 하더라도 프로그램을 실행하자마자 거의 통제 불능 상태로 되어 결국 시스템 재부팅을 하여야 할 것이다.

 

. 이렇듯 웹 프로세스를 통해 시스템의 메모리를 무한정 소모하는 것을 차단하기 위해서는 아파치의 설정 지시자 중에서 RLimitMEM(단위 byte) 사용하여 제한할 있는데, 이 지시자는 아파치 웹서버에서 생성된 프로세스가 작동할 때 소요 가능한 최대 메모리의 양을 제한하는 것으로, 메모리를 많이 소모하는 프로세스가 작동할 인자에서 지정된 메모리까지만 실행이 되고 이상 작동하지 않도록 준다.

 

 

예를 들어 httpd.conf

RLimitMEM 20480000 21504000

 

<Directory /home/special/public_html/*>

RLimitMEM 51200000 52224000

</Directory>

 

와 같이 설정하였다면 모든 디렉토리에서는 메모리를 20메가나 최대 21메가까지만 사용이 가능하고 /home/special/public_html/* 디렉토리 이하에 있는 데이터에 접근 시에는 특별히 50메가까지 메모리 이용이 가능하게 된다.

 

실제로 위와 같이 설정 후 memory.cgi를 웹에서 호출하면 아래와 같이 일정량의 메모리만 사용되고 중단하는 것을 확인할 수 있다.

 

.

 

 

1fc6203501b3fa7ade448d379ebc6817_1655442879_6701.png
 

[그림] memory.cgi 실행화면

 

이와 비슷한 인자로 CPU 점유시간을 제한하는 RLimitCPU와 사용자당 프로세스의 개수를 제한할 수 있는 RLimitNPROC이 있는데, 간단한 사용 예는 다음과 같다.

 

<VirtualHost 192.168.1.3>

DocumentRoot /www/htdocs

ServerName www.server.com

RLimitCPU 30 30

RLimitMEM 25000000 25000000

RLimitNPROC 20 20

</VirtualHost>

 

RLimitCPU 지시자를 이용하면 특정 프로세스가 과도한 CPU 타임을 소모할 경우 이의 사용을 제한하기 위한 용도로 사용할 수 있는데, 위와 같이 30초로 지정하였을 경우 특정 프로세스가 비정상적으로 오랫동안 CPU 타임을 소모할 경우 30초 후에는 자동으로 종료될 것이다.

 

. 마찬가지로 RLimitNPROC 지시자를 이용하면 특정 도메인에서 사용가능한 프로세스의 수를 제한함으로써 특정 도메인으로 접속이 몰리는 것을 제한할 수 있지만 잘 사용되는 것 같지는 않다.

 

RLimitCPU RLimitNPROC 지시자에 대한 보다 자세한 내용은

http://httpd.apache.org/docs-2.0/mod/core.html를 참고하기 바란다.

 

 

 

참고로 ps를 실행했을 때 %CPU%MEM가 보이는데, %MEM10 이상의 수치가 보인다면 시스템의 성능이 급격하게 저하되므로 즉각적인 조치가 필요하지만, %CPU99%라고 해서 크게 문제되지는 않는다.

 

일반적으로 %CPU99라고 하더라도 빨리 처리하고 끝내면 되기 때문이며 이는 지극히 정상적인 상황이다.

 

. 다만 %CPU도 높으면서 TIME 수치도 높다면 문제가 될 수 있을 것이다.

 

. 만약 nobody로 실행되는 httpd 프로세스에서 간헐적으로 많은 메모리(아래의 경우 %MEM2이상)를 소모하여 서버의 부하를 유발한다면 아래의 스크립트를 cron에 설정하여 강제로 죽이도록 하는 것도 좋다.

 

 

kill -9 `ps aux | grep nobody | awk '{if ($4 > 2) print $2}' `

 

LimitRequestBody

 

LimitRequestBody는 클라이언트가 서버의 CGI등을 통해 POSTPUT 메소드를 이용할 때 전송되는 총 메시지의 크기를 byte 단위로 정의하는 것으로 무한대를 의미하는 0부터 2,147,483,647(2Giga)까지 설정할 수 있는데, 이 지시자를 이용하여 대용량의 파일을 업/다운로드 하는 형태의 서비스 거부 공격을 차단할 수 있다.

 

. 이를 설정하는 방법은 httpd.conf 를 열어 아래의 라인을 추가하면 된다.

 

 

 

<Directory />

LimitRequestBody 7168000

</Directory>

<Directory /home/special/>

LimitRequestBody 10240000

</Directory>

 

위와 같이 LimitRequestBody 지시자를 설정하면 아파치 웹 서버를 이용하여 업/다운로드 하는 모든 파일의 사이즈를 7M로 제한하고, /home/special/ 이하에 대해서는 10M로 제한하게 된다.

 

위와 같이 설정하였을 때 지정된 사이즈를 초과하는 파일을 업/다운로드 할 때에는 “413 Request entity too large“라는 에러 메시지가 뜨며 업/다운로드가 되지 않는다.

 

그런데, 이 설정을 하지 않았는데도 불구하고 일정 용량 이상의 파일은 업/다운로드가 안 되는 경우가 있는데, 이는 특히 php를 사용할 경우 php 자체의 환경 변수로 인하여 업로드가 안 될 수도 있다.

 

. 이러한 경우에는 php.ini 파일에서 아래 변수의 값들을 살펴보기 바란다.

 

 

post_max_size =

upload_max_filesize =

memory_limit =

 

LimitRequestFields

 

LimitRequestFieldshttp 요청 메시지에 보이는 헤더 필드의 개수를 제한하는 지시자로서 기본 값은 100으로 지정되어 있는데, 실제로 정상적인 요청 수치는 약 20 정도이기 때문에 이 값을 적당하게 설정할 필요가 있다.

 

. LimitRequestFields 40 정도로 설정하면 무리가 없다. 만약에 LimitRequestFields에서 지정한 필드 값보다 큰 경우에는 “400 Bad Request. request failed: error reading the headers”라는 메시지가 뜨게 된다.

 

 

 

 

LimitRequestFieldsize

 

LimitRequestFieldsizehttp 요청 메시지에 보이는 헤더의 사이즈를 제한하는 지시자로서 기본 값은 최대값이기도 한 8190 byte인데, 이 값은 정상적인 상황에서는 사용하지 않는 큰 수치이므로 100 정도로 적당하게 설정하여 사용한다.

 

 

 

LimitRequestline

 

LimitRequestlineHTTP 메소드, URI 그리고 프로토콜 버전 정보 등 http 요청 라인의 사이즈를 제한하는 지시자로서 기본 값은 최대값이기도 한 8190 byte인데, 이 값은 정상적인 상황에서는 사용하지 않는 큰 수치이므로 500 정도로 적당하게 설정하여 사용한다.

 

아무리 GET 메소드를 이용한다 하더라도 정상적인 상황에서는 이 값을 초과하지는 않을 것이다.

 

관련자료

댓글 0
등록된 댓글이 없습니다.

공지사항


뉴스광장


  • 전체 회원수 59,444 명
  • 전체 게시물 30,916 개
  • 전체 댓글수 11,873 개