이 글은 https://www.tokyobranch.net/archives/6693https://www.tokyobranch.net/archives/7066 에 대한 반박글입니다. (네. 떡밥을 물고 말았습니다)

떡밥을 물며

트위터를 하다가 케케묵은 떡밥인 PHP 떡밥을 보았습니다. 보통 때 같았다면, 그냥 그저 그런 떡밥으로 넘겼겠지만 루아에 고통받고 있던 터라 빡치는 김에 떡밥을 물고 결국 이 글을 씁니다.

해당 글에서는 PHP 를 써야 하는 이유로

  1. PHP 는 언어 정책이 보수적이다
  2. 보안이나 성능 이슈는 PHP가 알아서 해 줄 것이다.
  3. PHP 를 세계적으로 많이 쓰는 이유가 뭐겠느냐
  4. PHP가 ORM과 같은 노가다를 대신 해준다

정도를 들고 있지만, 사실 이것들은 잘못되었습니다.

1. 언어 정책이 보수적이다

음 PHP 의 언어 정책에 정확한 이름을 붙이자면 그건 방임이지 보수적임이 아니겠죠

PHP는 많은 메소드들을 deprecation 시켰지만, 이를 제 때 처리하지 않았습니다. 대표적으로 mysql_* 함수들의 deprecation1 이 있는데, 이는 2012년도에 결정됬음에도, 이를 7.0 에 와서야 치웁니다2.

그리고 많은 수의 PHP개발자들은 Deprecation 경고에 신경을 전혀 쓰지 않았고, 그 결과는 Deprecated 된 함수의 지속적인 사용을 유발했습니다.

2. 보안 이슈를 PHP가 알아서 해 줄 것이다.

PHP는 보안 이슈에 있어서는 쥐약입니다. PHP언어 자체의 일관성이 상실된3 점이 프로그래머의 무관심과 시너지를 일으켜, 정말 골 때리는 문제를 양산하게 됩니다.

지천에 널려 있는 자질 미만의 프로그래머들이 ‘싼’ 코드들이 일으키는 SQLi 문제는 일단 넘어가더라도(SQL 에 $_COOKIE 를 concat 한다던가) 언어 자체에서 의도치 않은 함정들이 있습니다. 대표적 예로 PHP의 $_GET $_POST $_COOKIE 등이 있는데, 대부분의 경우, 이 변수들의 값은 문자열의 어레이입니다.

하지만 아래와 같은 쿼리 스트링이4 들어오면 어떻게 될까요?

http://example.com/vuln.php?query[()%20{%20example;};echo%20\%22Content-type:%20text/plain\%22;%20echo;%20echo;%20/bin/cat%20/etc/shadow%22]=test

이 쿼리 스트링을

print("\$_GET = ");
$foo=array();

print_r($_GET);
print('
'); print_r($_GET['query']);

에 넣어 보면 다음과 같은 결과를 출력합니다

$_GET = Array ( [query] => Array ( [() { example;};echo \"Content-type: text/plain\"; echo; echo; /bin/cat /etc/shadow"] => test ) )
Array ( [() { example;};echo \"Content-type: text/plain\"; echo; echo; /bin/cat /etc/shadow"] => test ) 

대부분의 경우, $_GET 과 같은 변수로 문자열이 들어올 것으로 예상하고 코드를 작성하기에 이는 잠재적으로 문제를 일으킬 가능성을 증가시킵니다.

이 부분 이외에도, PHP가 암묵적으로 eval() 을 하는 것과 같은 효과를 내는 부분은 또 있습니다

PHP 에서 다음 코드를 실행시키면 어떻게 될까요

$foo = 'phpinfo';
$foo();

놀랍게도 phpinfo() 가 실행되게 됩니다. 이는 잘못 짜여진 코드에서 Function injection5 과 같은 공격을 허용하게 됩니다.

이러한 공격을 막기 위해서는, 입력을 점검하고 필터링하는 노력이 필요한데, 이는 다른 언어에서는 필요 없는 (입력이 string 인지 object 인지 함수인지 체크하는) 작업입니다.

C 같은 경우는, 문자열을 함수 포인터로 잡아 호출하려 할 경우, SIGSEGV 를 내며 죽을 것입니다.
Javascript 는 “foo is not a function” 이라는 오류를 내며 죽을 것입니다.
Java 는 아예 컴파일이 안 될 것입니다.

또한 대부분의 웹 프레임워크들은 PHP처럼 유저 인풋을 어레이로 취급해서 어레이를 만들어준다던가 하는 미친 짓을 하지 않습니다. 예로, NodeJS 의 대표적인 웹 프레임워크인 ExpressJS 의 경우, req.query[“blah[]”] 와 같은 식으로 유저 인풋을 멋대로 가공하지 않고 그대로 넘겨주게 됩니다. 2017-02-17 갱신: ExpressJS 의 경우, ?foo=1&foo=2 와 같은 경우에 foo = [1, 2] 와 같이 결과를 냅니다6. 이는 프로그램이 예상하지 않은 곳에서 크래시날 수 있게 하는 문제를 유발할 수 있습니다. 하지만 PHP 처럼 eval 에 준하는 오류를 일으키진 않으므로 상대적으로 안전합니다.

결국 PHP를 사용함에 있어, 다른 언어를 사용할 때와 비교해서 더 많은 보일러플레이트 코드가 발생하게 됩니다.

3. PHP를 많이 쓴다

PHP 는 인터넷 붐 초창기에 빠르게 퍼져나갔습니다. 결국 악화가 양화를 구축하게 된 대표적 사례라고 할 수 있을 법한 케이스죠.
언어가 많이 쓰인다고 해서 언어의 구림을 커버칠 순 없습니다.

3-1. WP, FB가 PHP를 쓴다.

네 MOSFET만을 가지고 손으로 CPU를 만들 수도7 있죠. 이쑤시개를 가지고도 건축을 할 수 있을 거고요. 헥사 에디터만 있어도 로버의 라디오를 리프로그램할 수 있겠죠8.

하지만 우리가 고수준 언어를 쓰는 이유는 효율성 때문입니다. 그리고 PHP는 많은 보일러플레이트 생성과, 템플릿과 코드의 혼합으로 이를 해칩니다.

실제로 Automattic 의 Jetpack 코드는 PHP프로젝트 치고는 깔끔한 편입니다. 하지만, 이를 수정하려고 하는 작업은, PHP의 템플릿과 코드의 혼합으로 인해 굉장히 까다롭게 됩니다.

또한 안전을 위한, 혹은 동작에 필수적인 수 많은 보일러플레이트들의 존재는 프로그래머가 실수할 가능성을 증가시킵니다. 이는 결국 효율성 저하로 이어지고요.

Facebook 은 PHP를 견디다 못해 직접 언어를 만든 케이스입니다. HHVM 을 만들면서 PHP와 연동이 가능한 Hacklang9 을 만들어서 사용하고 있죠. Facebook 을 두고 PHP를 사용하는 회사라고 부르기엔 이젠 무리가 있겠네요.

4. PHP는 ORM 같은 노가다를 대신 해 준다.

원 글이 ORM이라 부르는 것을 봅시다

그런데 이 과정에는 최소한 두가지의 노가다 작업이 필요합니다. 유저에게서 입력받은 데이터가 정상인지 체크하는 Server side validation과, DB의 데이터를 프로그램에서 사용할 수 있는 형식으로 변환해 주는(퉁쳐서 ORM이라고 부르기도 하는) 작업입니다. 이 이외에도 수많은 노가다가 존재합니다만(파일 업로드 처리라던가 pagination이라던가 나열하면 끝이 없습니다.) 이 두가지는 작업량도 많은데다 이미 존재하는 사양을 코드로 변환하는 단순한 과정이기에 재미도 없습니다. 이 중에서 Server side validation은 모든 입력을 대상으로 하지 않으면 보안에 구멍이 생기지만, 안해도 어차피 티가 잘 안나는 작업이라서 누구에게도 인기가 없습니다.

DB 의 데이터를 프로그램에서 사용할 수 있는 형식으로 번환해 주는 것은 데이터베이스 드라이버지, ORM 이 아닙니다. ORM은 보통 Getter 와 Setter 를 통해 SQL을 생성하고, 이를 쿼리하고, 이 결과를 프로그래머가 지정한 구조대로 맞추어 주는 것을 말합니다.

결국, 편하게 DB를 액세스하기 위해서는 ORM 라이브러리를 사용해야 하는 것은 변하지 않습니다.

또한, 위에서도 짚었지만, PHP는 보일러플레이트 필요로 인해 오히려 프로그래머의 노가다를 증가시키지, 감소시키진 않습니다. 유지보수성 낮은 PHP에게 정신력을 빼앗기는 꼴이죠.

마무리

다르다와 틀리다는 다릅니다. 하지만 PHP는 틀렸습니다.

물론 모던 PHP가 PHP5.x 시절의 과오를 고치려고 하는 것은 맞습니다. 하지만, 이제 다른 웹 개발 분야에서 뜨는 언어에 비해 PHP가 가지는 장점은 많이 흐려진 상태입니다. Deploy 에서도 버전별로 갈리고, 프로덕션 환경에서는 PHP5.x 와 PHP7 간의 호환성 문제 덕분에 닭장 웹호스팅 (Cafe24 의 PHP호스팅을 생각하시면 됩니다) 같은 상품 중에서 모던 PHP를 제공하는 부류는 소수입니다.

개인적인 생각으로는, 언어 자체에 결점을 많이 가지고 있는 PHP가 이제 디플로이마저 어려우면, PHP의 메리트는 이제 굉장히 낮다고 생각합니다. 차라리 Python 이나 Javascript (NodeJS) 나 다른 좋은(그리고 제정신인) 고수준 언어를 쓰시고 정 안되면 C(libcgi) 로 짜세요(비꼼) 그게 더 정신력 코스트를 아낄 수 있을 겁니다.

덧:
count() 가 7.2 들어서 제 정신을 차리려고 하는 건 좋은데 워드프레스 코드조차 해당 코드에서 터지는군요

PHP 좋아하시는 분들은 count() 에 Countable IF 를 달고 있지 않은 객체가 들어와도 오류가 나지 않고 0을 반환하고 심지어 미정의된 변수가 들어와도 0을 반환하는 이런 API 가 정녕 사람이 제 정신을 가지고 작성한 API 명세라고 생각하십니까?

  1. https://wiki.php.net/rfc/mysql_deprecation
  2. https://wiki.php.net/rfc/remove_deprecated_functionality_in_php7
  3. https://php-a-fractal-of-bad-design-kr.github.io/ 참조
  4. http://security.stackexchange.com/a/128594
  5. https://www.owasp.org/index.php?title=Function_Injection
  6. https://github.com/expressjs/express/issues/1824
  7. http://monster6502.com/
  8. 마션의 그 장면
  9. http://hacklang.org/