암호화 저장 모델
Error Reporting
데이터베이스 보안
PHP Manual

## SQL 인젝션

많은 웹 개발자가 SQL 질의가 공격받을 수 있다는 점을 간과하고, SQL 질의를 신뢰할 수 있는 명령으로 가정합니다. 이로 인해 SQL 질의에서 접근 제어를 우회할 수 있여, 일반적인 인증과 인증 확인을 무시하고, 종종 SQL 질의가 OS 단계 명령을 할 수 있도록 합니다.

직접 SQL 명령 인젝션은 공격자가 숨겨진 데이터를 노출하거나, 취약한 부분을 덮어쓰거나, 데이터베이스에 위험한 시스템 단계 명령을 실행하게 하는 SQL 명령을 생성하거나 대체하는 기술입니다. 어플리케이션이 사용자 입력을 받아서, 이를 SQL 질의를 만들 떄 정적 인수로 조합함으로써 일어납니다. 유감스럽게도, 아래 예제들은 실제 이야기를 기반으로 하고 있습니다.

입력 검증이 없고 데이터베이스에 슈퍼유저나 사용자를 만들 수 있는 사용자로 접속하는 경우, 공격자가 데이터베이스에 슈퍼유저를 만들 수 있습니다.

Example #1 결과셋을 페이지로 나눔 ... 그리고 슈퍼유저 만들기 (PostgreSQL)

`<?php
$offset = $argv[0]; // 주의, 입력 검증 없음!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>`

일반 사용자는 URL에 $offset이 인코드되어 있는 'next', 'prev' 링크를 클릭합니다. 스크립트는 $offset이 정수라고 생각합니다. 그러나, 누군가가 다음처럼 URL에 추가적인 urlencode() 형식을 덧붙이면 어떻게 될까요?

    0;
    insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
        select 'crack', usesysid, 't','t','crack'
        from pg_shadow where usename='postgres';
    --

이렇게 되면, 스크립트에서 슈퍼유저 권한을 공격자에게 주게 됩니다. 0;가 유효한 offset으로 제공되어 원래 질의를 유효하게 하고 정료하는 점에 주의하십시오.

> Note: SQL 해석기에서 개발자가 쓴 쿼리의 나머지 부분을 무시하게 하는 일반적인 방법은 --를 붙이는 것이며, 이는 SQL에서 주석 부호입니다.

패스워드를 얻는 방법 중 하나는 검색 결과 페이지를 우회하는 것입니다. 공격자에게 필요한 것은 제출한 변수 중 하나라도 제대로 다뤄지지 않으면서 SQL 구문에 사용되는 것입니다. 이러한 필터는 일반적으로 SELECT 구문에서 WHERE, ORDER BY, LIMIT, OFFSET에 사용됩니다. 데이터베이스가 UNION 구조를 지원하면, 공격자는 원래 질의에 전체 질의를 덧붙여서 임의의 테이블에서 패스워드를 얻을 수 있습니다. 암호화된 패스워드 필드를 강력히 권합니다.

Example #2 글을 출력함 ... 그리고 패스워드도 (모든 데이터베이스 서버)

`<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'
ORDER BY $order LIMIT $limit, $offset;";
$result = odbc_exec($conn, $query);
?>`

질의에서 정적인 부분은 모든 패스워드를 가져오는 SELECT 구문과 조합될 수 있습니다:

    '
    union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
    --

이 질의('와 --로 다룸)가 $query에서 사용하는 변수 중 하나에 할당되면, 질의 괴물이 깨어납니다.

SQL UPDATE도 공격받을 수 있습니다. 이런 질의를 잘라내어서 완전한 새 질의를 덧붙일 수 있습니다. 또한 공격자가 SET 절을 다룰 수도 있습니다. 이 경우 질의를 성공적으로 변경하기 위하여 일부 스키마 정보를 가지고 있어야 합니다. 이는 폼 변수명을 조사하거나, 브루트 포스로 얻을 수 있습니다. 패스워드와 사용자이름을 저장하는 필드의 이름 규칙은 그리 많지 않습니다.

Example #3 패스워드 재설정에서 ... 더 많은 권한 얻기 (모든 데이터베이스 서버)

`<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>`

악의적인 사용자가 $uid에 ' or uid like'%admin'; -- 값을 넣어서 관리자 패스워드를 변경하거나, $pwd에 "hehehe', admin='yes', trusted=100 "(마지막 공백 포함)을 설정하여 권한을 얻을 수도 있습니다. 그러면, 질의가 다음처럼 꼬입니다:

`<?php
// $uid == ' or uid like'%admin%'; --
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";
// $pwd == "hehehe', admin='yes', trusted=100 "
$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE
...;";
?>`

데티어베이스 호스트의 OS 등급 명령에 접근하는 섬뜩한 예제입니다.

Example #4 데이터베이스 호스트 OS 공격하기 (MSSQL 서버)

`<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>`

공격자가 $prod에 a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- 값을 제출하면, $query는:

`<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
?>`

MSSQL 서버는 로컬 계정 데이터베이스에 새 사용자를 추가하는 명령을 포함한 SQL 구문을 실행하게 됩니다. 어플리케이션이 sa로 실행되고 MSSQLSERVER 서비스가 적합한 권한을 가지고 있으면, 공격자는 머신에 접근할 수 있는 계정을 가지게 됩니다.

> Note: 위 예제 중 일부는 특정 데이터베이스 서버에 묶여 있습니다. 이것은 다른 서버에 유사한 공격이 불가능하다는 의미가 아닙니다. 데이터베이스 서버가 다른 방식으로 비슷한 취약점을 가질 수 있습니다.

### 기술 피하기

대부분의 예제에서 공격자가 데이터베이스 스키마에 대한 정보를 가지고 있어야 한다고 말할 수 있을겁니다. 맞습니다, 그러나 스키마가 어떻게 유출되는지 알 수 없습니다. 그리고 유출되면, 데이터베이스가 노출됩니다. 오픈 소스를 사용하거나 CMS나 포럼에 공개되어 있는 데이터베이스 접근 패키지를 가지고 있으면, 침입자가 간단히 코드의 사본을 구할 수 있습니다. 나쁘게 디자인되어 있으면 그 자체로도 보안 위협이 됩니다.

이러한 공격은 주로 보안을 염두에 두지 않고 쓰여진 코드 취약점에 기반하고 있습니다. 어떠한 입력도 믿지 마십시오. 특히 클라이언트측에서 오는 입력은 믿지 마십시오. select, hidden input 필드, 쿠키도 마찬가지입니다. 첫번째 예제에서 그러한 질의가 재앙을 일으킬 수 있는 점을 보여주고 있습니다.

- 데이터베이스에 슈퍼유저나 데이터베이스 주인으로 접속하지 마십시오. 항상 매우 제한된 권한을 가진 특별 사용자를 사용하십시오.
- 주어진 입력이 기대한 자료형인지 확인하십시오. PHP는 넓은 범위의 입력 검증 함수를 가지고 있습니다. 가장 간단한 변수 관련 함수와 문자형 함수(예. 각각 is_numeric(), ctype_digit())부터 펄 호환 정규표현식 지원까지 있습니다.

- 어플리케이션이 숫자 입력을 기다린다면, is_numeric()으로 데이터를 검사하거나, settype()으로 자료형을 바꾸거나, sprintf()에서 숫자 표현을 사용하십시오.

  Example #5 페이지 질의를 작성하는 더 안전한 방법

  `<?php
  settype($offset, 'integer');
  $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
  // please note %d in the format string, using %s would be meaningless
  $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
  $offset);
  ?>`

- 숫자가 아닌 사용자 제공 값은 데이터베이스 한정 문자열 회피 함수로 인용하십시오. (예. mysql_real_escape_string(), sql_escape_string() 등) 데이터베이스 한정 문자열 회피 함수가 없으면, addslashes()와 str_replace() 함수를 사용할 수 있습니다. (데이터베이스 형식에 따릅니다) 첫번째 예제를 확인하십시오. 예제가 보여주듯이, 질의의 정적 부분에 따옴표를 붙이는건 충분하지 않으며, 질의를 쉽게 깰 수 있습니다.
- 어떠한 데이터베이스 한정 정보라도 출력하지 마십시오. 특히, 스키마에 대해서는 어떠한 경우라도 안됩니다. 오류 보고와 오류 다루기와 기록 함수를 참고하십시오.
- 저장된 프로시저와 이전에 정의한 커서를 사용해서 추상적인 데이터 접근을 할 수도 있습니다. 이로써 사용자가 직접 테이블이나 뷰에 접근할 수 없게 할 수 있지만, 이 해결책은 다른 문제가 있습니다.

다만, 스크립트 안에서나 (기록을 지원한다면) 데이터베이스에서 질의를 기록하는 것이 도움이 됩니다. 반면, 기록은 유해한 시도를 방지하지는 못합니다. 그러나 어떠한 어플리케이션이 우회되었는지 추적할 때는 도움이 됩니다. 기록 자체는 유용하지 않으며, 포함한 정보가 중요합니다. 일반적으로 더 자세한 정보가 낫습니다.

암호화 저장 모델
Error Reporting
데이터베이스 보안
PHP Manual