mysql 공격 해킹 - PHP에서 SQL 삽입을 방지하려면 어떻게해야합니까?





14 Answers

경고 : 이 답변의 샘플 코드 (질문의 샘플 코드와 같은)는 PHP의 mysql 확장을 사용합니다.이 확장은 PHP 5.5.0에서 사용되지 않으며 PHP 7.0.0에서 완전히 제거되었습니다.

최신 버전의 PHP를 사용하고 있다면, 아래에서 설명하는 mysql_real_escape_string 옵션은 더 이상 사용할 수 없게됩니다 ( mysqli::escape_string 은 현대의 기능입니다). 요즈음 mysql_real_escape_string 옵션은 구 버전 PHP의 레거시 코드에만 의미가 있습니다.

unsafe_variable 특수 문자를 이스케이프 처리하거나 매개 변수화 된 쿼리를 사용하는 두 가지 옵션이 있습니다. 둘 다 SQL 주입으로부터 당신을 보호 할 것입니다. 매개 변수화 된 쿼리는 더 나은 방법이지만 PHP를 사용하기 전에 PHP에서 더 새로운 mysql 확장으로 변경해야합니다.

먼저 영향을 덜받는 문자열을 이스케이프 처리합니다.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

mysql_real_escape_string 함수의 세부 사항도 보라.

매개 변수가있는 쿼리를 사용하려면 MySQL 함수 대신 MySQLi 를 사용해야합니다. 예를 재 작성하려면 다음과 같은 것이 필요할 것입니다.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

mysqli::prepare 는 당신이 읽고 싶어 할 핵심 기능이다.

또한 다른 사람들이 제안한 것처럼 PDO 와 같은 추상화 계층을 유용하고 쉽게 사용할 수 있습니다.

요청한 사례는 매우 간단하며 복잡한 사례의 경우 더 복잡한 접근법이 필요할 수 있습니다. 특히:

  • 사용자 입력을 기반으로 SQL 구조를 변경하려는 경우 매개 변수화 된 쿼리는 도움이되지 않으며 필요한 이스케이프 처리는 mysql_real_escape_string 다루지 않습니다. 이런 경우에는 허용 된 값만 '안전'하도록 허용하기 위해 허용 목록을 통해 사용자 입력을 전달하는 것이 좋습니다.
  • 조건에서 사용자 입력의 정수를 사용하고 mysql_real_escape_string 접근법을 사용하면 Polynomial 에 설명 된 문제가 발생합니다. 정수가 따옴표로 묶이지 않기 때문에이 경우가 더 까다 롭습니다. 따라서 사용자 입력에 숫자 만 포함되어 있는지 확인하여 처리 할 수 ​​있습니다.
  • 내가 알지 못하는 다른 경우가있을 수 있습니다. this 문제는 발생할 수있는보다 미세한 문제에 대한 유용한 자료입니다.
기법 query string

사용자 입력을 수정하지 않고 SQL 쿼리에 삽입하면 다음 예제와 같이 응용 프로그램이 SQL 주입에 취약 해집니다.

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

그것은 사용자가 value'); DROP TABLE table;-- 와 같은 것을 입력 할 수 있기 때문입니다 value'); DROP TABLE table;-- value'); DROP TABLE table;-- , 쿼리는 다음과 같습니다.

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

이것이 일어나지 않도록하기 위해 할 수있는 일은 무엇입니까?




PDO (PHP Data Objects)를 사용하여 매개 변수가있는 SQL 쿼리를 실행하는 것이 좋습니다.

SQL 인젝션을 방지 할뿐만 아니라 쿼리 속도를 높입니다.

그리고 mysql_ , mysqli_ , pgsql_ 함수가 아닌 PDO를 사용하면 드물게 데이터베이스 공급자를 전환해야하는 상황에서 데이터베이스에서 앱을 좀 더 추상화 할 수 있습니다.




보시다시피 사람들은 준비된 문장을 최대한 사용하는 것이 좋습니다. 잘못된 것은 아니지만 쿼리가 프로세스 당 한 번만 실행 되면 약간의 성능 저하가 발생합니다.

나는이 문제에 직면 해 있었지만 해커들이 따옴표 사용을 피하는 방법과 같이 매우 정교한 방법으로 해결했다고 생각합니다. 필자는 에뮬레이트 된 준비 문과 함께이 기능을 사용했습니다. 저는 모든 종류의 가능한 SQL 주입 공격을 막기 위해 그것을 사용합니다.

내 접근 방식 :

  • 입력이 정수가 될 것으로 예상되면 실제로 정수 여야합니다. PHP와 같은 변수 형 언어에서는 이것이 매우 중요합니다. sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input); 예를 들어 다음과 같이 간단하고 강력한 솔루션을 사용할 수 있습니다 sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • 당신이 정수 hex 로부터 다른 것을 기대한다면. 16 진수라면 모든 입력을 완벽하게 피할 수 있습니다. C / C ++에는 mysql_hex_string() 이라는 함수가 있습니다. PHP에서는 bin2hex() 사용할 수 있습니다.

    mysql_real_escape_string 을 사용하더라도 동일한 용량 ((2*input_length)+1) 을 할당해야하므로 이스케이프 된 문자열의 원래 길이가 2x 크기가되므로 걱정하지 마십시오.

  • 이 16 진수 메서드는 이진 데이터를 전송할 때 자주 사용되지만 SQL 주입 공격을 막기 위해 모든 데이터에 사용하지 않아도됩니다. 0x 데이터를 추가하거나 UNHEX 대신 MySQL 함수를 UNHEX 합니다.

예를 들어, 쿼리는 다음과 같습니다.

SELECT password FROM users WHERE name = 'root'

될 것입니다:

SELECT password FROM users WHERE name = 0x726f6f74

또는

SELECT password FROM users WHERE name = UNHEX('726f6f74')

16 진수는 완벽한 탈출구입니다. 주사 할 방법이 없어.

UNHEX 함수와 0x 접두어의 차이점

코멘트에 약간의 토론이 있었기 때문에 나는 그것을 명확하게 밝히고 싶습니다. 이 두 가지 접근법은 매우 유사하지만 몇 가지면에서 조금 다릅니다.

** 0x ** 접두어는 char, varchar, text, block, binary 등과 같은 데이터 열에 만 사용할 수 있습니다.
또한 빈 문자열을 삽입하려는 경우 약간 복잡합니다. 완전히 '' 교체해야합니다. 그렇지 않으면 오류가 발생합니다.

UNHEX ()모든 열에서 작동합니다. 빈 문자열에 대해 걱정할 필요가 없습니다.

16 진법은 종종 공격으로 사용됩니다.

이 16 진법은 정수가 문자열과 같고 mysql_real_escape_string 하여 이스케이프 처리되는 SQL 주입 공격으로 자주 사용됩니다. 그런 다음 따옴표를 사용하지 않아도됩니다.

예를 들어, 다음과 같이하면됩니다.

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

공격은 당신을 아주 쉽게 주사 할 수 있습니다 . 스크립트에서 반환 된 다음 주입 코드를 고려하십시오.

SELECT ... where id = -1 union 모두 information_schema.tables에서 table_name을 선택합니다.

이제 테이블 구조 만 추출하면됩니다.

SELECT ... where id = -1 union 모두 information_schema.column의 column_name을 선택합니다. 여기서 table_name = 0x61727469636c65

그리고 원하는 데이터를 선택하십시오. 멋지지 않니?

그러나 주사 할 수있는 사이트의 코더가 16 진수 일 경우 쿼리는 다음과 같을 것이므로 주사는 불가능합니다 : SELECT ... WHERE id = UNHEX('2d312075...3635')




보안 경고 :이 답변은 보안 모범 사례와 일치하지 않습니다. 이스케이프 처리는 SQL 삽입을 방지하기에는 부적절합니다 . 대신 준비된 명령문을 사용하십시오. 아래에 설명 된 전략을 따른다는 위험 부담이 있습니다. (또한 mysql_real_escape_string() 은 PHP 7에서 삭제되었습니다.)

다음과 같이 기본적인 것을 할 수 있습니다 :

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

이것은 모든 문제를 해결하지는 못하지만 아주 좋은 디딤돌입니다. 변수의 존재, 형식 (숫자, 문자 등)을 검사하는 것과 같은 명백한 항목은 생략했습니다.




매개 변수화 된 쿼리 및 입력 유효성 검사가 필요합니다. mysql_real_escape_string() 이 사용 되었더라도 SQL 주입이 발생할 수있는 시나리오는 많이 있습니다.

이러한 예제는 SQL 삽입에 취약합니다.

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

또는

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

두 경우 모두 캡슐화를 보호하기 위해 사용할 수 없습니다.

Source : 예기치 않은 SQL 삽입 (이스 케이 핑이 충분하지 않을 때)




나는 보안상의 관점에서 저장 프로 시저 ( MySQL은 5.0 이후 프로 시저 지원을 저장했다)를 선호한다 - 장점은 -

  1. 대부분의 데이터베이스 ( MySQL 포함)는 사용자 액세스가 저장 프로 시저 실행으로 제한되도록합니다. 세분화 된 보안 액세스 제어는 권한 공격의 수준 상승을 방지하는 데 유용합니다. 이렇게하면 손상된 응용 프로그램이 데이터베이스에 대해 SQL을 직접 실행할 수 없게됩니다.
  2. 이들은 응용 프로그램에서 원시 SQL 쿼리를 추상화하므로 데이터베이스 구조에 대한 정보를 응용 프로그램에서 사용할 수 있습니다. 따라서 사람들이 데이터베이스의 기본 구조를 이해하고 적절한 공격을 설계하는 것이 더 어려워집니다.
  3. 매개 변수 만 허용하므로 매개 변수가있는 쿼리의 장점이 있습니다. 물론 - IMO에서는 여전히 입력을 위생해야합니다 - 특히 저장 프로 시저 내부에서 동적 SQL을 사용하는 경우.

단점은 -

  1. 그것들 (저장 프로 시저)은 유지하기가 어렵고 매우 빠르게 번식하는 경향이 있습니다. 이로 인해 문제를 관리하게됩니다.
  2. 동적 쿼리에 적합하지 않습니다. 동적 코드를 매개 변수로 허용하도록 빌드 된 경우 많은 이점이 무효화됩니다.



나는 누군가가 PHP와 MySQL 또는 다른 데이터베이스 서버를 사용하기를 원한다면 나는 생각한다.

  1. PDO (PHP Data Objects) 학습에 대해 생각하십시오 - 여러 데이터베이스에 대한 일관된 액세스 방법을 제공하는 데이터베이스 액세스 계층입니다.
  2. MySQLi 학습에 대해 생각해보십시오.MySQLi
  3. strip_tags , mysql_real_escape_string() 또는 가변 숫자 인 경우 와 같이 기본 PHP 함수를 사용하십시오 (int)$foo. PHP here 변수 유형에 대한 자세한 내용은 here . PDO 나 MySQLi 같은 라이브러리를 사용한다면, 항상 PDO::quote()mysqli_real_escape_string() .

라이브러리 예제 :

---- PDO

----- 자리 표시 자 없음 - SQL 주입에 익숙해졌습니다! 그것은 나쁜

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- 이름없는 자리 표시 자

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- 명명 된 자리 표시 자

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

추신 :

PDO는이 전투에서 쉽게 승리합니다. 12 개의 다른 데이터베이스 드라이버와 명명 된 매개 변수에 대한 지원으로, 작은 성능 손실을 무시하고 API에 익숙해 질 수 있습니다. 보안 관점에서 볼 때 개발자가 사용되는 방식대로 사용하는 한 둘 다 안전합니다.

그러나 PDO와 MySQLi는 모두 매우 빠르지 만 MySQLi는 벤치 마크에서는 준비되지 않은 문장이 ~ 2.5 %, 준비된 문장이 ~ 6.5 % 정도로 현저히 빠르게 수행됩니다.

그리고 모든 쿼리를 데이터베이스에 테스트하십시오. 이것은 주사를 방지하는 더 좋은 방법입니다.




RedisMemcached 와 같은 캐시 엔진을 이용하려면 아마도 DALMP가 선택 될 수 있습니다. 그것은 순수 MySQLi 사용합니다 . 이것을 확인하십시오 : PHP를 사용하는 MySQL 용 DALMP Database Abstraction Layer.

또한 동적 u 리를 빌드 할 수 있도록 u 리를 준비하기 전에 인수를 '준비'할 수 있으며 결국에는 완전히 준비된 명령문 u 리를 갖습니다. PHP를 사용하는 MySQL 용 DALMP 데이터베이스 추상화 계층.




PDO를 사용하는 방법을 잘 모르는 사람들을 위해 ( mysql_필자는 함수 에서 왔음 ) 단일 파일 인 매우 간단한 PDO 래퍼 를 만들었습니다 . 애플리케이션이 수행해야하는 모든 일반적인 작업을 얼마나 쉽게 수행하는지 보여주기 위해 존재합니다. PostgreSQL, MySQL 및 SQLite에서 작동합니다.

기본적으로 PDOPDO PDO 기능을 실생활에 사용하여 원하는 형식으로 값을 저장하고 검색하는 방법을 쉽게 이해할 수 있습니다 .

나는 하나의 칼럼을 원한다.

$count = DB::column('SELECT COUNT(*) FROM `user`);

나는 배열 (키 => 값) 결과를 원한다. (즉, 선택 상자 만들기)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

단일 행 결과를 원합니다.

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

나는 일련의 결과를 원한다.

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));



이 문제에 대한 간단한 대안은 데이터베이스 자체에 적절한 권한을 부여하여 해결할 수 있습니다. 예 : mysql 데이터베이스를 사용하는 경우 터미널이나 제공된 UI를 통해 데이터베이스에 들어가 다음 명령을 따르십시오.

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

이렇게하면 사용자는 지정된 쿼리에만 국한 될 수 있습니다. 삭제 권한을 제거하면 데이터가 PHP 페이지에서 실행 된 쿼리에서 삭제되지 않습니다. 두 번째로해야 할 일은 권한을 플러시하여 mysql이 권한과 업데이트를 새로 고치는 것이다.

FLUSH PRIVILEGES; 

flush 에 대한 자세한 정보 .

사용자에 대한 현재 권한을 보려면 다음 쿼리를 실행하십시오.

select * from mysql.user where User='username';

GRANT 대해 자세히 알아보십시오 .




간단한 방법은 CodeIgniter 또는 Laravel 과 같은 PHP 프레임 워크를 사용하는 것입니다.이 프레임 워크는 필터링 및 활성 레코드와 같은 inbuilt 기능을 가지고 있으므로 이러한 뉘앙스에 대해 걱정할 필요가 없습니다.




** 경고 :이 대답에 설명 된 접근법은 매우 특정한 시나리오에만 적용되며 SQL 주입 공격은 주입 할 수있는 것에 의존하지 않으므로 안전하지 않습니다 X=Y. **

공격자가 PHP의 $_GET변수 또는 URL의 쿼리 문자열을 통해 양식을 해킹하려고 시도 하는 경우 보안되지 않은 공격자 를 잡을 수 있습니다.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

때문에 1=1, 2=2, 1=2, 2=1, 1+1=2, 등 ... 공격자의 SQL 데이터베이스에 대한 일반적인 질문이다. 어쩌면 그것은 또한 많은 해킹 응용 프로그램에 의해 사용됩니다.

그러나주의해야합니다. 사이트에서 안전한 쿼리를 다시 작성하면 안됩니다. 위의 코드를 다시 작성하거나 리디렉션, 당신에게 팁을주고있다 (당신에 따라 다름) 것을 해킹 특정 공격자의 저장하는 페이지에 동적 쿼리 문자열 IP 주소를 그들의 쿠키, 역사, 브라우저를하거나, 또는 민감한 다른 정보를 제공하므로 나중에 자신의 계정을 금지하거나 관계 당국에 연락하여 대응할 수 있습니다.




좋은 아이디어는 Idiorm 과 같은 '객체 관계형 매퍼' 를 사용하는 것 Idiorm .

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

SQL 인젝션뿐만 아니라 구문 오류로부터도 당신을 구할 수 있습니다! 또한 메서드 체인을 사용하여 한 번에 여러 결과에 대한 작업을 필터링하거나 적용 할 수있는 모델 모음을 지원합니다.




몇 년 전에이 작은 기능을 작성했습니다.

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

이렇게하면 한 줄짜리 C #에서 String.Format과 같은 문을 실행할 수 있습니다.

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

그것은 가변 타입을 고려하여 빠져 나간다. 테이블, 열 이름을 매개 변수화하려고하면 모든 문자열이 잘못된 구문 인 따옴표로 묶이므로 실패합니다.

보안 업데이트 : 이전 str_replace버전에서는 {#} 토큰을 사용자 데이터에 추가하여 주입을 허용했습니다. preg_replace_callback교체가이 토큰을 포함하면 이 버전은 문제를 일으키지 않습니다.




Related