etc./StackOverFlow

PHP에서 mysql_* 함수를 사용하면 안 되는 이유는 무엇입니까?

청렴결백한 만능 재주꾼 2021. 11. 23. 03:44
반응형

질문자 :Madara's Ghost


mysql_* 함수를 사용하면 안 되는 기술적 이유는 무엇입니까? (예: mysql_query() , mysql_connect() 또는 mysql_real_escape_string() )?

내 사이트에서 작동하더라도 다른 것을 사용해야 하는 이유는 무엇입니까?

내 사이트에서 작동하지 않는 경우 다음과 같은 오류가 발생하는 이유는 무엇입니까?

경고: mysql_connect(): 해당 파일이나 디렉토리가 없습니다.



MySQL 확장:

  • 활발히 개발 중이 아님
  • PHP 5.5(2013년 6월 릴리스)부터 공식적으로 더 이상 사용되지 않습니다.
  • PHP 7.0(2015년 12월 출시)에서 완전히 제거 되었습니다.
    • 이는 2018년 12월 31일 현재 지원되는 PHP 버전에 존재하지 않음을 의미합니다. 지원하는 PHP 버전을 사용하고 있다면 보안 문제가 해결되지 않는 버전을 사용하고 있는 것입니다.
  • OO 인터페이스가 부족합니다.
  • 지원하지 않음:
    • 비차단, 비동기 쿼리
    • 준비된 명령문 또는 매개변수화된 쿼리
    • 저장 프로시저
    • 여러 문
    • 업무
    • "새" 암호 인증 방법(MySQL 5.6에서는 기본적으로 켜져 있음, 5.7에서는 필요함)
    • MySQL 5.1 이상의 새로운 기능

더 이상 사용되지 않으므로 이를 사용하면 코드가 미래에 대비할 수 없습니다.

준비된 명령문에 대한 지원 부족은 별도의 함수 호출로 수동으로 이스케이프하는 것보다 외부 데이터를 이스케이프 및 인용하는 더 명확하고 오류가 발생하기 쉬운 방법을 제공하기 때문에 특히 중요합니다.

SQL 확장 비교를 참조하십시오.


Community Wiki

PHP는 MySQL에 연결할 수 있는 세 가지 API를 제공합니다. 이것은 mysql (PHP 7부터 제거됨), mysqliPDO 확장입니다.

mysql_* 함수는 매우 인기가 있었지만 더 이상 사용을 권장하지 않습니다. 문서 팀은 데이터베이스 보안 상황에 대해 논의하고 있으며 일반적으로 사용되는 ext/mysql 확장에서 벗어나도록 사용자를 교육하고 있습니다( php.internals: deprecating ext/mysql 확인 ).

그리고 나중에 PHP 개발자 팀은 mysql_connect() , mysql_pconnect() ext/mysql 내장된 암시적 연결 기능을 통해 사용자가 MySQL에 연결할 때 E_DEPRECATED 오류를 생성하기로 결정했습니다.

ext/mysql 은 공식적으로 PHP 5.5부터 더 이상 사용되지 않으며PHP 7 부터 제거되었습니다.

빨간 상자가 보이시나요?

mysql_* 함수 매뉴얼 페이지로 이동하면 더 이상 사용해서는 안 된다고 설명하는 빨간색 상자가 표시됩니다.


ext/mysql 에서 벗어나는 것은 보안뿐만 아니라 MySQL 데이터베이스의 모든 기능에 액세스하는 것과 관련이 있습니다.

ext/mysql 은 MySQL 3.23 용으로 구축되었으며 그 이후로 거의 추가되지 않았지만 대부분 이 이전 버전과의 호환성을 유지하여 코드를 유지 관리하기가 약간 더 어렵게 만듭니다. ext/mysql 지원하지 않는 누락된 기능은 다음과 같습니다( PHP 매뉴얼에서 ).

mysql_* 함수를 사용하지 않는 이유 :

  • 활발히 개발 중이 아님
  • PHP 7부터 제거됨
  • OO 인터페이스가 부족합니다.
  • 비차단, 비동기 쿼리를 지원하지 않습니다.
  • 준비된 명령문 또는 매개변수화된 쿼리를 지원하지 않습니다.
  • 저장 프로시저를 지원하지 않음
  • 여러 문을 지원하지 않습니다.
  • 거래를 지원하지 않습니다
  • MySQL 5.1의 모든 기능을 지원하지 않습니다.

Quentin의 답변에서 인용 된 위의 요점

준비된 명령문에 대한 지원 부족은 별도의 함수 호출로 수동으로 이스케이프하는 것보다 외부 데이터를 이스케이프 및 인용하는 더 명확하고 오류가 발생하기 쉬운 방법을 제공하기 때문에 특히 중요합니다.

SQL 확장 비교를 참조하십시오.


사용 중단 경고 표시 안 함

MySQLi / PDO 로 변환되는 동안 E_DEPRECATED E_DEPRECATED: 를 제외하도록 php.ini error_reporting 을 설정하여 억제할 수 있습니다.

 error_reporting = E_ALL ^ E_DEPRECATED

이것은 또한 MySQL 이외의 것에 대한 것일 수 있는 다른 사용 중단 경고를 숨깁니다. ( PHP 매뉴얼에서 )

기사 PDO 대 MySQLi: 어느 것을 사용해야 합니까? Dejan Marjanovic의 선택에 도움이 될 것입니다.

그리고 더 좋은 방법은 PDO . 저는 지금 간단한 PDO 튜토리얼을 작성하고 있습니다.


간단하고 짧은 PDO 튜토리얼


Q. 가장 먼저 떠오른 질문은 'PDO'가 무엇인가요?

A. " PDO - PHP Data Objects -는 여러 데이터베이스에 대한 균일한 액세스 방법을 제공하는 데이터베이스 액세스 계층입니다."

대체 텍스트


MySQL에 연결

mysql_* 함수를 사용하거나 이전 방식으로 말할 수 있습니다(PHP 5.5 이상에서는 더 이상 사용되지 않음)

 $link = mysql_connect('localhost', 'user', 'pass'); mysql_select_db('testdb', $link); mysql_set_charset('UTF-8', $link);

PDO PDO 개체를 만들기만 하면 됩니다. 생성자는 데이터베이스 소스를 지정하기 위한 매개변수를 허용합니다. PDO DSN (데이터 소스 이름) 및 선택적으로 username , password 인 4개의 매개변수를 사용합니다.

DSN 제외한 모든 것에 익숙하다고 생각합니다. PDO 새로운 기능입니다. DSN 은 기본적으로 사용할 드라이버와 연결 세부 정보를 PDO 에 알려주는 일련의 옵션입니다. 추가 참조는 PDO MySQL DSN 을 확인하십시오.

 $db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

참고: charset=UTF-8 사용할 수도 있지만 때로는 오류가 발생하므로 utf8 을 사용하는 것이 좋습니다.

연결 오류가 있는 경우 Exception 추가로 처리하기 위해 catch할 수 PDOException

좋은 글 : 연결 및 연결 관리 ¶

여러 드라이버 옵션을 네 번째 매개변수에 배열로 전달할 수도 있습니다. PDO 를 예외 모드로 전환하는 매개변수를 전달하는 것이 좋습니다. 일부 PDO 드라이버는 기본 준비된 문을 지원하지 않으므로 PDO 는 준비의 에뮬레이션을 수행합니다. 또한 이 에뮬레이션을 수동으로 활성화할 수 있습니다. 기본 서버 측 준비된 문을 사용하려면 명시적으로 false 설정해야 합니다.

MySQL 드라이버에서 기본적으로 활성화되어 있는 에뮬레이션 준비를 끄는 PDO 안전하게 사용하려면 에뮬레이션 준비를 꺼야 합니다.

에뮬레이션 준비를 꺼야 하는 이유는 나중에 설명하겠습니다. 이유를 찾으려면 이 게시물을 확인하세요.

권장하지 않는 MySQL 을 사용하는 경우에만 사용할 수 있습니다.

다음은 이를 수행하는 방법의 예입니다.

 $db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 'username', 'password', array(PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

PDO 생성 후 속성을 설정할 수 있습니까?

setAttribute 메소드를 사용하여 PDO 생성 후에 일부 속성을 설정할 수도 있습니다.

 $db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 'username', 'password'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

오류 처리


오류 처리는 mysql_* 보다 PDO 에서 훨씬 쉽습니다.

mysql_* 사용할 때의 일반적인 관행은 다음과 같습니다.

 //Connected to MySQL $result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die() 우리가 일을 처리 할 수 있기 때문에 오류를 처리하는 좋은 방법 아니다 die . 스크립트를 갑자기 종료한 다음 일반적으로 최종 사용자에게 표시하고 싶지 않은 오류를 화면에 표시하고 피비린내 나는 해커가 스키마를 발견하게 합니다. mysql_* 함수의 반환 값을 종종 mysql_error() 와 함께 사용하여 오류를 처리할 수 있습니다.

PDO 는 더 나은 솔루션인 예외를 제공합니다. PDO 모든 작업 try - catch 블록으로 래핑되어야 합니다. PDO 를 세 가지 오류 모드 중 하나로 강제할 수 있습니다. 세 가지 오류 처리 모드는 다음과 같습니다.

  • PDO::ERRMODE_SILENT . 그것은 단지 오류 코드를 설정하고 각 결과를 확인하고 $db->errorInfo(); mysql_* 오류 세부 정보를 얻으려면.
  • PDO::ERRMODE_WARNING E_WARNING 올립니다. (런타임 경고(치명적이지 않은 오류). 스크립트 실행이 중단되지 않습니다.)
  • PDO::ERRMODE_EXCEPTION : 예외를 던집니다. PDO에서 발생한 오류를 나타냅니다. 자신의 코드에서 PDOException 을 throw해서는 안 됩니다. PHP의 예외에 대한 자세한 내용은 예외를 참조하십시오. 그것은 매우 유사하게 작동 or die(mysql_error()); , 잡히지 않을 때. or die() 와 달리 PDOException 은 그렇게 하기로 선택하면 적절하게 포착되고 처리될 수 있습니다.

잘 읽었습니다 :

좋다:

 $stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); $stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING ); $stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

그리고 아래와 같이 try - catch 래핑할 수 있습니다.

 try { //Connect as appropriate as above $db->query('hi'); //Invalid query! } catch (PDOException $ex) { echo "An Error occured!"; //User friendly message/message you want to show to user some_logging_function($ex->getMessage()); }

지금 당장 try - catch 로 처리할 필요가 없습니다. 적절한 시간에 잡을 수 있지만 try - catch 를 사용하는 것이 좋습니다. PDO 을 호출하는 함수 외부에서 잡는 것이 더 합리적일 수 있습니다.

 function data_fun($db) { $stmt = $db->query("SELECT * FROM table"); return $stmt->fetchAll(PDO::FETCH_ASSOC); } //Then later try { data_fun($db); } catch(PDOException $ex) { //Here you can handle error and show message/perform action you want. }

또한 by or die() mysql_* 처럼 말할 수 있지만 실제로는 다양합니다. display_errors off 오류 로그를 읽으면 프로덕션에서 위험한 오류 메시지를 숨길 수 있습니다.

이제 위의 모든 내용을 읽은 후 아마도 다음과 같이 생각할 것입니다. 간단한 SELECT , INSERT , UPDATE 또는 DELETE 문을 기울이기 시작하려고 할 때 도대체 그게 무엇입니까? 걱정하지 마세요.


데이터 선택

PDO 선택 이미지

mysql_* 에서 수행하는 작업은 다음과 같습니다.

 <?php $result = mysql_query('SELECT * from table') or die(mysql_error()); $num_rows = mysql_num_rows($result); while($row = mysql_fetch_assoc($result)) { echo $row['field1']; }

이제 PDO 에서 다음과 같이 할 수 있습니다.

 <?php $stmt = $db->query('SELECT * FROM table'); while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo $row['field1']; }

또는

 <?php $stmt = $db->query('SELECT * FROM table'); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); //Use $results

참고 : 아래와 같은 메서드( query() )를 사용하는 경우 이 메서드는 PDOStatement 객체를 반환합니다. 따라서 결과를 가져오려면 위와 같이 사용하십시오.

 <?php foreach($db->query('SELECT * FROM table') as $row) { echo $row['field1']; }

PDO 데이터에서는 ->fetch() 메서드를 통해 가져옵니다. 가져오기를 호출하기 전에 가장 좋은 방법은 데이터를 가져오는 방법을 PDO에 알려주는 것입니다. 아래 섹션에서 이에 대해 설명합니다.

가져오기 모드

위의 fetch()fetchAll() 코드 PDO::FETCH_ASSOC 사용에 유의하십시오. 이것은 PDO 가 필드 이름을 키로 사용하여 행을 연관 배열로 반환하도록 지시합니다. 다른 가져오기 모드도 많이 있으므로 하나씩 설명하겠습니다.

먼저 가져오기 모드를 선택하는 방법을 설명합니다.

 $stmt->fetch(PDO::FETCH_ASSOC)

위에서 fetch() . 다음을 사용할 수도 있습니다.

이제 가져오기 모드로 이동합니다.

  • PDO::FETCH_ASSOC : 결과 집합에 반환된 대로 열 이름으로 인덱싱된 배열을 반환합니다.
  • PDO::FETCH_BOTH (기본값): 결과 집합에 반환된 대로 열 이름과 0-인덱싱된 열 번호로 인덱싱된 배열을 반환합니다.

더 많은 선택이 있습니다! PDOStatement Fetch 문서 에서 모두 읽어보십시오. .

행 수 얻기 :

반환된 행 수를 얻기 위해 mysql_num_rows 를 사용하는 대신, 다음과 PDOStatement rowCount() 수행할 수 있습니다.

 <?php $stmt = $db->query('SELECT * FROM table'); $row_count = $stmt->rowCount(); echo $row_count.' rows selected';

마지막으로 삽입된 ID 가져오기

 <?php $result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')"); $insertId = $db->lastInsertId();

문 삽입 및 업데이트 또는 삭제

PDO 이미지 삽입 및 업데이트

mysql_* 함수에서 우리가 하는 일은 다음과 같습니다:

 <?php $results = mysql_query("UPDATE table SET field='value'") or die(mysql_error()); echo mysql_affected_rows($result);

그리고 pdo에서는 다음과 같은 방법으로 동일한 작업을 수행할 수 있습니다.

 <?php $affected_rows = $db->exec("UPDATE table SET field='value'"); echo $affected_rows;

위의 쿼리에서 PDO::exec 는 SQL 문을 실행하고 영향을 받는 행의 수를 반환합니다.

삽입 및 삭제는 나중에 다룹니다.

위의 방법은 쿼리에서 변수를 사용하지 않을 때만 유용합니다. 그러나 쿼리에서 변수를 사용해야 할 때 위와 같이 시도하지 마십시오. 준비된 명령문이나 매개 변수화된 명령문 이 있습니다.


준비된 진술

Q. 준비된 진술은 무엇이며 왜 필요한가요?
A. 준비된 문장은 미리 컴파일된 SQL 문장으로, 데이터만 서버에 보내 여러 번 실행할 수 있다.

준비된 문을 사용하는 일반적인 작업 흐름은 다음과 같습니다( Wikipedia three 3 point 에서 인용 ).

  1. 준비 : 명령문 템플릿은 애플리케이션에서 생성되어 데이터베이스 관리 시스템(DBMS)으로 전송됩니다. 매개변수, 자리 표시자 또는 바인드 변수라고 하는 특정 값은 지정되지 않은 상태로 남습니다(아래에 ?

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. DBMS는 명령문 템플릿에 대해 구문 분석, 컴파일 및 쿼리 최적화를 수행하고 실행하지 않고 결과를 저장합니다.

  3. Execute : 나중에 애플리케이션이 매개변수에 대한 값을 제공(또는 바인딩)하고 DBMS가 명령문을 실행합니다(결과를 반환할 수 있음). 응용 프로그램은 다른 값으로 원하는 만큼 문을 실행할 수 있습니다. 이 예에서 첫 번째 매개변수에 'Bread'를 제공하고 두 번째 매개변수에 1.00

SQL에 자리 표시자를 포함하여 준비된 명령문을 사용할 수 있습니다. 기본적으로 자리 표시자가 없는 세 가지가 있습니다(위의 변수를 사용하여 이것을 시도하지 마십시오).

Q. 이제 명명된 자리 표시자는 무엇이며 어떻게 사용합니까?
A. 명명된 자리 표시자. 물음표 대신 콜론이 앞에 오는 설명적인 이름을 사용하십시오. 이름 자리 표시자의 위치/값 순서는 신경 쓰지 않습니다.

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

실행 배열을 사용하여 바인딩할 수도 있습니다.

 <?php $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name"); $stmt->execute(array(':name' => $name, ':id' => $id)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

OOP 친구를 위한 또 다른 좋은 기능은 속성이 명명된 필드와 일치한다고 가정할 때 명명된 자리 표시자가 데이터베이스에 개체를 직접 삽입할 수 있다는 것입니다. 예를 들어:

 class person { public $name; public $add; function __construct($a,$b) { $this->name = $a; $this->add = $b; } } $demo = new person('john','29 bla district'); $stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)"); $stmt->execute((array)$demo);

Q. 이제 이름 없는 자리 표시자는 무엇이며 어떻게 사용합니까?
A. 예를 들어보겠습니다.

 <?php $stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)"); $stmt->bindValue(1, $name, PDO::PARAM_STR); $stmt->bindValue(2, $add, PDO::PARAM_STR); $stmt->execute();

그리고

 $stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)"); $stmt->execute(array('john', '29 bla district'));

위에서 볼 수 ? 이름 자리 표시자와 같은 이름 대신. 이제 첫 번째 예에서 다양한 자리 표시자( $stmt->bindValue(1, $name, PDO::PARAM_STR); )에 변수를 할당합니다. 그런 다음 해당 자리 표시자에 값을 할당하고 명령문을 실행합니다. 두 번째 예에서 첫 번째 배열 요소는 첫 번째 ? 그리고 두 번째에서 두 번째로 ? .

참고 : 이름 없는 자리 표시자 PDOStatement::execute() 메서드에 전달하는 배열 요소의 올바른 순서를 처리해야 합니다.


SELECT , INSERT , UPDATE , DELETE 준비된 쿼리

  1. SELECT :

     $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name"); $stmt->execute(array(':name' => $name, ':id' => $id)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  2. INSERT :

     $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)"); $stmt->execute(array(':field1' => $field1, ':field2' => $field2)); $affected_rows = $stmt->rowCount();
  3. DELETE :

     $stmt = $db->prepare("DELETE FROM table WHERE id=:id"); $stmt->bindValue(':id', $id, PDO::PARAM_STR); $stmt->execute(); $affected_rows = $stmt->rowCount();
  4. UPDATE :

     $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?"); $stmt->execute(array($name, $id)); $affected_rows = $stmt->rowCount();

노트:

그러나 PDO 및/또는 MySQLi 는 완전히 안전하지 않습니다. 답변을 확인하십시오. PDO 준비된 명령문이 SQL 주입을 방지하기에 충분합니까? ircmaxell에 의해 . 또한 나는 그의 대답에서 일부를 인용하고 있습니다.

 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $pdo->query('SET NAMES GBK'); $stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1"); $stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));

NullPoiиteя

먼저 우리가 모든 사람에게 제공하는 표준 의견부터 시작하겠습니다.

새 코드에서 mysql_* 함수를 사용하지 마십시오 . 더 이상 유지 관리되지 않으며 공식적으로 더 이상 사용되지 않습니다 . 빨간 박스 보이 시죠? 대신 준비된 명령문에 대해 알아보고 PDO 또는 MySQLi를 사용하십시오. 이 기사 는 어느 것을 결정하는 데 도움이 될 것입니다. PDO를 선택했다면 여기 좋은 튜토리얼이 있습니다.

이를 한 문장씩 살펴보고 설명하자면 다음과 같습니다.

  • 더 이상 유지 관리되지 않으며 공식적으로 더 이상 사용되지 않습니다.

    이것은 PHP 커뮤니티가 이러한 아주 오래된 기능에 대한 지원을 점차적으로 중단하고 있음을 의미합니다. PHP의 미래(최신) 버전에는 존재하지 않을 가능성이 높습니다! 이러한 기능을 계속 사용하면 머지 않은 미래에 코드가 손상될 수 있습니다.

    새로운! - ext/mysql은 이제 공식적으로 PHP 5.5부터 더 이상 사용되지 않습니다!

    더 새로운! ext/mysql 은 PHP 7에서 제거되었습니다 .

  • 대신 준비된 진술에 대해 배워야 합니다.

    mysql_* 확장은 준비된 문을 지원하지 않습니다 . 이는 무엇보다도 SQL 주입 에 대한 매우 효과적인 대책입니다. 공격자가 스크립트에 액세스하고 데이터베이스에서 가능한 쿼리 를 수행할 수 있도록 하는 MySQL 종속 응용 프로그램의 매우 심각한 취약점을 수정했습니다.

    자세한 내용 은 PHP에서 SQL 주입을 방지하려면 어떻게 해야 하나요?를 참조하세요.

  • 빨간 상자가 보이시나요?

    mysql 함수 매뉴얼 페이지로 이동하면 더 이상 사용해서는 안 된다고 설명하는 빨간색 상자가 표시됩니다.

  • PDO 또는 MySQLi 사용

    더 우수하고 강력하며 잘 구축된 대안인 PDO - 데이터베이스 상호 작용에 대한 완전한 OOP 접근 방식을 제공하는 PHP Database Object 와 MySQL 전용 개선 사항인 MySQLi 가 있습니다.


Madara's Ghost

사용의 용이성

분석적이고 종합적인 이유는 이미 언급되었습니다. 새로 온 사람들에게는 날짜가 지정된 mysql_ 함수 사용을 중단하는 더 중요한 인센티브가 있습니다.

최신 데이터베이스 API는 사용 하기가 더 쉽습니다.

대부분 코드를 단순화할 수 있는 바인딩된 매개 변수입니다. 그리고 우수한 튜토리얼(위에서 보았듯이)을 사용 하면 PDO 로의 전환이 지나치게 힘들지 않습니다.

그러나 한 번에 더 큰 코드 기반을 다시 작성하려면 시간이 걸립니다. 이 중간 대안을 위한 이유:

mysql_* 대신 동등한 pdo_* 함수

< pdo_mysql.php > 를 사용하면 최소한의 노력으로 이전 mysql_ 함수에서 전환할 수 있습니다. mysql_ 대응물을 대체하는 pdo_ 함수 래퍼를 추가합니다.

  1. 단순히 include_once( "pdo_mysql.php" ); 데이터베이스와 상호 작용해야 하는 각 호출 스크립트에서.

  2. 모든 곳 mysql_ 함수 접두사를 제거하고 pdo_ 바꾸십시오.

    • mysql_ connect()pdo_ connect()
    • mysql_ query()pdo_ query()
    • mysql_ num_rows()pdo_ num_rows()
    • mysql_ insert_id()pdo_ insert_id()
    • mysql_ fetch_array()pdo_ fetch_array()
    • mysql_ fetch_assoc()pdo_ fetch_assoc()
    • mysql_ real_escape_string()pdo_ real_escape_string()
    • 등등...

  3. 코드는 비슷하게 작동하고 여전히 대부분 동일하게 보입니다.

     include_once("pdo_mysql.php"); pdo_connect("localhost", "usrABC", "pw1234567"); pdo_select_db("test"); $result = pdo_query("SELECT title, html FROM pages"); while ($row = pdo_fetch_assoc($result)) { print "$row[title] - $row[html]"; }

자.
귀하의 코드는 PDO를 사용하고 있습니다.
이제 실제로 사용할 때입니다.

바인딩된 매개변수는 사용하기 쉽습니다.

덜 다루기 힘든 API만 있으면 됩니다.

pdo_query() 는 바인딩된 매개변수에 대한 매우 손쉬운 지원을 추가합니다. 이전 코드를 변환하는 것은 간단합니다.

변수를 SQL 문자열 밖으로 이동하십시오.

  • pdo_query() 쉼표로 구분된 함수 매개변수로 추가하십시오.
  • 물음표를 놓으 ? 변수가 이전에 있던 자리 표시자로.
  • 이전에 문자열 값/변수를 묶은 ' 작은 따옴표를 제거하십시오.

코드가 길수록 이점이 더 분명해집니다.

종종 문자열 변수는 SQL에 삽입되는 것이 아니라 그 사이에 이스케이프 호출로 연결됩니다.

 pdo_query("SELECT id, links, html, title, user, date FROM articles WHERE title='" . pdo_real_escape_string($title) . "' OR id='". pdo_real_escape_string($title) . "' AND user <> '" . pdo_real_escape_string($root) . "' ORDER BY date")

? 자리 표시자가 적용되면 귀찮게 할 필요가 없습니다.

 pdo_query("SELECT id, links, html, title, user, date FROM articles WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

pdo_*는 여전히 또는 .
그냥 변수를 탈출하고 동일한 쿼리에 바인딩하지 않습니다.

  • 자리 표시자 기능은 그 뒤에 있는 실제 PDO에서 제공합니다.
  • :named 자리 표시자 목록도 허용됩니다.

더 중요한 것은 $_REQUEST[] 변수를 모든 쿼리 뒤에 안전하게 전달할 수 있다는 것입니다. 제출된 <form> 필드가 데이터베이스 구조와 정확히 일치하면 훨씬 더 짧습니다.

 pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

너무 단순합니다. mysql_ 제거하고 탈출하려는 이유에 대해 다시 작성하는 조언과 기술적인 이유를 좀 더 살펴보겠습니다.

sanitize() 함수 수정 또는 제거

바인딩된 매개변수를 사용하여 mysql_ pdo_query 로 변환했으면 pdo_real_escape_string 호출을 제거하십시오.

특히 당신은 어떤 수정해야 sanitize 또는 clean 또는 filterThis 또는 clean_data 한 형태 또는 다른에서 날짜가 기입 된 튜토리얼에 의해 광고 등의 기능을 :

 function sanitize($str) { return trim(strip_tags(htmlentities(pdo_real_escape_string($str)))); }

여기서 가장 눈에 띄는 버그는 문서가 없다는 것입니다. 더 중요한 것은 필터링 순서가 정확히 잘못된 순서였습니다.

  • deprecatedly : 올바른 순서했을 stripslashes 가장 안쪽의 호출로, 다음 trim 이후, strip_tags , htmlentities 출력 컨텍스트, 오직 마지막으로 _escape_string 의 응용 프로그램으로 직접 intersparsing SQL을보다 선행해야한다.

  • 그러나 첫 번째 단계로 _real_escape_string 호출을 제거하십시오.

  • 데이터베이스와 애플리케이션 흐름이 HTML 컨텍스트에 안전한 문자열을 예상한다면 지금 sanitize() 함수를 유지해야 할 수도 있습니다. 앞으로 HTML 이스케이프만 적용된다는 주석을 추가합니다.

  • 문자열/값 처리는 PDO 및 해당 매개변수화된 명령문에 위임됩니다.

  • stripslashes() 에 대한 언급이 있는 경우 더 높은 수준의 감독을 나타낼 수 있습니다.

    magic_quotes에 대한 역사적인 메모. 해당 기능은 더 이상 사용되지 않습니다. 그러나 종종 실패한 보안 기능으로 잘못 묘사됩니다. 그러나 magic_quote는 테니스 공이 영양 공급원으로 실패한 것처럼 실패한 보안 기능입니다. 그것은 단순히 그들의 목적이 아니 었습니다.

    PHP2/FI의 원래 구현에서는 " 따옴표가 자동으로 이스케이프되어 양식 데이터를 msql 쿼리에 직접 전달하는 것이 더 쉬워집니다 "와 함께 명시적으로 도입했습니다. 특히 ASCII만 지원 하는 mSQL 과 함께 사용하는 것이 실수로 안전했습니다.
    그런 다음 PHP3/Zend는 MySQL용 magic_quotes를 다시 도입하고 이를 잘못 문서화했습니다. 그러나 원래는 보안을 위한 것이 아니라 단지 편의 기능이었습니다.

준비된 진술의 차이점

문자열 변수를 SQL 쿼리에 스크램블하면 따라가기가 더 복잡해지지 않습니다. 또한 MySQL이 코드와 데이터를 다시 분리하는 것은 불필요한 노력입니다.

SQL 인젝션은 단순히 데이터가 코드 컨텍스트로 흘러들어갈 때입니다. 데이터베이스 서버는 PHP가 원래 쿼리 절 사이에 변수를 붙인 위치를 나중에 찾을 수 없습니다.

바인딩된 매개변수를 사용하여 PHP 코드에서 SQL 코드와 SQL 컨텍스트 값을 분리합니다. 그러나 뒤에서 다시 섞이지 않습니다(PDO::EMULATE_PREPARES 제외). 데이터베이스는 고정 SQL 명령과 1:1 변수 값을 수신합니다.

mysql_ 삭제의 가독성 이점에 주의해야 한다고 강조합니다. 이러한 가시적이고 기술적인 데이터/코드 분리로 인해 때때로 성능상의 이점(값만 다른 INSERT가 반복됨)이 있습니다.

매개변수 바인딩은 여전히 모든 SQL 인젝션에 대한 마법의 원스톱 솔루션이 아닙니다. 데이터/값에 대한 가장 일반적인 사용을 처리합니다. 그러나 열 이름/테이블 식별자를 화이트리스트에 추가하거나 동적 절 구성을 지원하거나 일반 배열 값 목록만 허용할 수는 없습니다.

하이브리드 PDO 사용

이러한 pdo_* 래퍼 함수는 코딩 친화적 스톱 갭 API를 만듭니다. (특이한 함수 시그니처 시프트가 MYSQLI 될 수 있었던 것과 거의 비슷합니다). 그들은 또한 대부분의 시간에 실제 PDO를 노출합니다.
재작성은 새로운 pdo_ 함수 이름을 사용하는 것으로 멈출 필요가 없습니다. 각 pdo_query()를 일반 $pdo->prepare()->execute() 호출로 하나씩 전환할 수 있습니다.

그러나 다시 단순화부터 시작하는 것이 가장 좋습니다. 예를 들어 일반적인 결과 가져오기:

 $result = pdo_query("SELECT * FROM tbl"); while ($row = pdo_fetch_assoc($result)) {

foreach 반복으로 대체할 수 있습니다.

 foreach ($result as $row) {

또는 직접적이고 완전한 어레이 검색:

 $result->fetchAll();

대부분의 경우 실패한 쿼리 후에 PDO 또는 mysql_이 제공하는 것보다 더 유용한 경고를 받게 됩니다.

다른 옵션

따라서 이것은 몇 가지 실용적인 mysql_ 을 삭제하는 가치 있는 경로를 시각화했습니다.

전환하는 것만으로는 충분하지 않습니다. pdo_query() 는 그것에 대한 프론트엔드이기도 합니다.

또한 매개변수 바인딩을 도입하거나 더 멋진 API에서 다른 것을 활용할 수 없다면 무의미한 전환입니다. 초보자분들이 더 실망하지 않도록 간단하게 표현했으면 좋겠습니다. (교육은 일반적으로 금지보다 효과적입니다.)

작동할 수 있는 가장 간단한 범주에 해당하지만 여전히 매우 실험적인 코드이기도 합니다. 그냥 주말에 썼어요. 그러나 많은 대안이 있습니다. PHP 데이터베이스 추상화를 위해 구글을 검색하고 조금 찾아보세요. 그러한 작업을 위한 훌륭한 라이브러리는 항상 있었고 앞으로도 많이 있을 것입니다.

데이터베이스 상호 작용을 더욱 단순화하려면 Paris/Idiorm 과 같은 매퍼를 사용해 볼 가치가 있습니다. 아무도 JavaScript에서 평범한 DOM을 더 이상 사용하지 않는 것처럼 요즘에는 원시 데이터베이스 인터페이스를 돌볼 필요가 없습니다.


mario

mysql_ 기능:

  1. 구식 - 더 이상 유지되지 않습니다.
  2. 다른 데이터베이스 백엔드로 쉽게 이동할 수 없습니다.
  3. 준비된 진술을 지원하지 않으므로
  4. 프로그래머가 연결을 사용하여 쿼리를 작성하도록 권장하여 SQL 주입 취약점으로 이어짐

Alnitak

기술적인 이유에 대해 말하자면, 극히 구체적이고 거의 사용되지 않는 몇 가지만 있습니다. 아마 평생 사용하지 않을 것입니다.
내가 너무 무지한 것일 수도 있지만, 나는 그런 것들을 사용할 기회가 없었습니다.

  • 비차단, 비동기 쿼리
  • 여러 결과 집합을 반환하는 저장 프로시저
  • 암호화(SSL)
  • 압축

그것들이 필요하다면 - 이것은 의심할 여지 없이 mysql 확장에서 더 세련되고 현대적으로 보이는 것으로 옮겨가는 기술적인 이유입니다.

그럼에도 불구하고 경험을 조금 더 어렵게 만들 수 있는 몇 가지 비기술적 문제도 있습니다.

  • 최신 PHP 버전에서 이러한 기능을 더 사용하면 더 이상 사용되지 않는 수준의 알림이 표시됩니다. 그들은 단순히 끌 수 있습니다.
  • 먼 미래에 기본 PHP 빌드에서 제거될 수 있습니다. mydsql ext가 PECL로 옮겨지고 모든 호스트가 PHP를 컴파일하는 데 기뻐할 것입니다. 수십 년 동안 사이트를 운영해온 클라이언트를 잃고 싶지 않기 때문입니다.
  • Stackoverflow 커뮤니티의 강력한 저항. Е이 정직한 기능을 언급할 때마다 엄격한 금기 사항이 적용된다는 말을 듣게 됩니다.
  • 평균적인 PHP 사용자라면 이러한 기능을 사용하는 아이디어는 오류가 발생하기 쉽고 잘못되었을 가능성이 큽니다. 잘못된 길을 가르쳐주는 수많은 튜토리얼과 매뉴얼 때문입니다. 기능 자체가 아니라 - 강조해야 하는 부분 - 사용 방식입니다.

이 후자의 문제가 문제입니다.
그러나 제 생각에는 제안된 솔루션도 더 좋지 않습니다.
모든 PHP 사용자가 한 번에 SQL 쿼리를 적절하게 처리하는 방법을 배우게 된다는 것은 너무 이상적인 꿈인 것 같습니다. 대부분 기계적으로 mysql_*을 mysqli_*로 변경 하고 접근 방식은 동일하게 유지 합니다. 특히 mysqli는 준비된 명령문 사용을 엄청나게 고통스럽고 번거롭게 만들기 때문입니다.
기본 준비된 문 은 SQL 주입으로부터 보호하기에 충분 하지 않으며 mysqli나 PDO도 솔루션을 제공하지 않는다는 것은 말할 것도 없습니다.

따라서 이 정직한 확장과 싸우는 대신 잘못된 관행과 싸우고 올바른 방식으로 사람들을 교육하는 것을 선호합니다.

또한 다음과 같이 거짓이거나 중요하지 않은 이유가 있습니다.

  • 저장 프로시저를 지원하지 않습니다(우리는 mysql_query("CALL my_proc");
  • 트랜잭션을 지원하지 않습니다(위와 동일).
  • 다중 명령문을 지원하지 않습니다(누가 필요합니까?)
  • 활발히 개발 중이 아닙니다(그래서 무엇을? 그것이 당신 에게 어떤 실질적인 영향을 미치나요?)
  • OO 인터페이스가 부족합니다(생성하는 데 몇 시간이 소요됨)
  • 준비된 명령문 또는 매개변수화된 쿼리를 지원하지 않습니다.

마지막은 흥미로운 점입니다. mysql ext는 기본 준비된 명령문을 지원하지 않지만 안전을 위해 필요한 것은 아닙니다. PDO와 마찬가지로 수동으로 처리되는 자리 표시자를 사용하여 준비된 명령문을 쉽게 가짜로 만들 수 있습니다.

 function paraQuery() { $args = func_get_args(); $query = array_shift($args); $query = str_replace("%s","'%s'",$query); foreach ($args as $key => $val) { $args[$key] = mysql_real_escape_string($val); } $query = vsprintf($query, $args); $result = mysql_query($query); if (!$result) { throw new Exception(mysql_error()." [$query]"); } return $result; } $query = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d"; $result = paraQuery($query, $a, "%$b%", $limit);

짜잔 , 모든 것이 매개변수화되고 안전합니다.

그러나 좋습니다. 설명서의 빨간색 상자가 마음에 들지 않으면 mysqli 또는 PDO 중에서 선택의 문제가 발생합니다.

답은 다음과 같을 것입니다.

  • 데이터베이스 추상화 계층 사용의 필요성을 이해하고 이를 생성하기 위한 API를 찾고 있다면 mysqli 가 실제로 많은 mysql 특정 기능을 지원하기 때문에 매우 좋은 선택입니다.
  • 대다수의 PHP 사용자와 마찬가지로 응용 프로그램 코드에서 원시 API 호출을 사용하는 경우(본질적으로 잘못된 관행) - 이 확장이 API가 아니라 semi-DAL인 것처럼 가장하기 때문에 PDO가 유일한 선택입니다. 여전히 불완전하지만 많은 중요한 기능을 제공하며, 그 중 두 가지는 PDO를 mysqli와 크게 구별합니다.

    • mysqli와 달리 PDO는 값으로 자리 표시자를 바인딩할 수 있으므로 매우 지저분한 코드의 여러 화면 없이 동적으로 빌드된 쿼리를 실행할 수 있습니다.
    • mysqli와 달리 PDO는 항상 간단한 일반적인 배열로 쿼리 결과를 반환할 수 있지만 mysqli는 mysqlnd 설치에서만 수행할 수 있습니다.

따라서 일반 PHP 사용자이고 기본 준비된 명령문을 사용할 때 많은 골치거리를 피하고 싶다면 PDO가 유일한 선택입니다.
그러나 PDO도 만병통치약이 아니며 어려움이 있습니다.
그래서 PDO 태그 위키 에서 모든 일반적인 함정과 복잡한 경우에 대한 솔루션을 작성했습니다.

그럼에도 불구하고 확장에 대해 이야기하는 모든 사람들은 항상 Mysqli와 PDO에 대한 두 가지 중요한 사실을 놓치고 있습니다.

  1. 준비된 진술 은 은총알이 아닙니다 . 준비된 문을 사용하여 바인딩할 수 없는 동적 식별자가 있습니다. 쿼리 작성을 어려운 작업으로 만드는 매개변수 수를 알 수 없는 동적 쿼리가 있습니다.

  2. mysqli_* 또는 PDO 함수는 애플리케이션 코드에 나타나지 않아야 합니다.
    바인딩, 루핑, 오류 처리 등의 모든 더러운 작업을 내부에서 수행하여 애플리케이션 코드를 DRY하고 깨끗하게 만드는 추상화 계층 이 애플리케이션 코드와 있어야 합니다. 특히 동적 쿼리 작성과 같은 복잡한 경우에 그렇습니다.

따라서 PDO 또는 mysqli로 전환하는 것만으로는 충분하지 않습니다. 코드에서 원시 API 함수를 호출하는 대신 ORM, 쿼리 빌더 또는 모든 데이터베이스 추상화 클래스를 사용해야 합니다.
그리고 반대로 - 애플리케이션 코드와 mysql API 사이에 추상화 계층이 있는 경우 - 실제로 어떤 엔진이 사용되는지는 중요하지 않습니다. 더 이상 사용되지 않을 때까지 mysql ext를 사용한 다음 모든 애플리케이션 코드를 그대로 유지하면서 추상화 클래스를 다른 엔진으로 쉽게 다시 작성할 수 있습니다.

다음은 이러한 추상화 클래스가 어떻게 되어야 하는지 보여주기 위해내 safemysql 클래스 를 기반으로 하는 몇 가지 예입니다.

 $city_ids = array(1,2,3); $cities = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

이 한 줄 을 PDO에 필요한 코드 양과 비교하십시오.
그런 다음 원시 Mysqli 준비된 명령문과 함께 필요한 엄청난 양의 코드 와 비교하십시오. 오류 처리, 프로파일링, 쿼리 로깅이 이미 내장되어 실행 중입니다.

 $insert = array('name' => 'John', 'surname' => "O'Hara"); $db->query("INSERT INTO users SET ?u", $insert);

이 모든 명명된 자리 표시자, 바인딩 및 쿼리 정의에서 모든 단일 필드 이름이 6~10번 반복되는 일반적인 PDO 삽입과 비교하십시오.

또 다른 예:

 $data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

PDO가 이러한 실제 사례를 처리하는 예는 거의 찾을 수 없습니다.
그리고 너무 장황하고 안전하지 않을 가능성이 큽니다.

따라서 다시 한 번 - 원시 드라이버가 관심을 가져야 하는 것이 아니라 추상화 클래스이며, 초심자 매뉴얼의 어리석은 예제뿐만 아니라 실제 문제를 해결하는 데 유용합니다.


Your Common Sense

많은 이유가 있지만 아마도 가장 중요한 이유는 이러한 함수가 준비된 명령문을 지원하지 않기 때문에 안전하지 않은 프로그래밍 관행을 조장한다는 것입니다. 준비된 명령문은 SQL 주입 공격을 방지하는 데 도움이 됩니다.

mysql_* 함수를 사용할 때 mysql_real_escape_string() 통해 사용자 제공 매개변수를 실행하는 것을 기억해야 합니다. 한 곳만 잊어버리거나 입력의 일부만 탈출하는 경우 데이터베이스가 공격을 받을 수 있습니다.

PDO 또는 mysqli 에서 준비된 명령문을 사용하면 이러한 종류의 프로그래밍 오류를 만들기가 더 어려워집니다.


Trott

(무엇보다도) 입력 데이터가 삭제되었는지 확인하는 것이 훨씬 더 어렵기 때문입니다. PDO 또는 mysqli에서와 같이 매개변수화된 쿼리를 사용하면 위험을 완전히 피할 수 있습니다.

예를 들어 누군가 "enhzflep); drop table users" 를 사용자 이름으로 사용할 수 있습니다. 이전 함수는 쿼리당 여러 명령문을 실행할 수 있도록 허용하므로 그와 같은 불쾌한 버그가 전체 테이블을 삭제할 수 있습니다.

mysqli의 PDO를 사용하는 경우 사용자 이름은 "enhzflep); drop table users" 됩니다.

bobby-tables.com을 참조하십시오.


enhzflep

이 답변은 잘못 작성된 PHP 사용자 검증 코드를 우회하는 것이 얼마나 사소한 일인지, 이러한 공격이 어떻게 작동하는지(그리고 무엇을 사용하는지), 이전 MySQL 기능을 안전한 준비된 명령문으로 대체하는 방법과 기본적으로 왜 StackOverflow 사용자인지 보여주기 위해 작성되었습니다. (아마도 많은 담당자가 있음) 코드를 개선하기 위해 질문을 하는 신규 사용자에게 짖고 있습니다.

먼저, 이 테스트 mysql 데이터베이스를 자유롭게 만드십시오(나는 광산 준비라고 불렀습니다):

 mysql> create table users( -> id int(2) primary key auto_increment, -> userid tinytext, -> pass tinytext); Query OK, 0 rows affected (0.05 sec) mysql> insert into users values(null, 'Fluffeh', 'mypass'); Query OK, 1 row affected (0.04 sec) mysql> create user 'prepared'@'localhost' identified by 'example'; Query OK, 0 rows affected (0.01 sec) mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option; Query OK, 0 rows affected (0.00 sec)

완료되면 PHP 코드로 이동할 수 있습니다.

다음 스크립트가 웹사이트의 관리자를 위한 확인 프로세스라고 가정하겠습니다(단순하지만 테스트를 위해 복사하여 사용하는 경우 작동함).

 <?php if(!empty($_POST['user'])) { $user=$_POST['user']; } else { $user='bob'; } if(!empty($_POST['pass'])) { $pass=$_POST['pass']; } else { $pass='bob'; } $database='prep'; $link=mysql_connect('localhost', 'prepared', 'example'); mysql_select_db($database) or die( "Unable to select database"); $sql="select id, userid, pass from users where userid='$user' and pass='$pass'"; //echo $sql."<br><br>"; $result=mysql_query($sql); $isAdmin=false; while ($row = mysql_fetch_assoc($result)) { echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>"; $isAdmin=true; // We have correctly matched the Username and Password // Lets give this person full access } if($isAdmin) { echo "The check passed. We have a verified admin!<br>"; } else { echo "You could not be verified. Please try again...<br>"; } mysql_close($link); ?> <form name="exploited" method='post'> User: <input type='text' name='user'><br> Pass: <input type='text' name='pass'><br> <input type='submit'> </form>

언뜻보기에 충분히 합법적 인 것처럼 보입니다.

사용자는 로그인과 비밀번호를 입력해야 하는 것 아닙니까?

훌륭합니다. 다음 항목을 입력하지 마십시오.

 user: bob pass: somePass

제출합니다.

출력은 다음과 같습니다.

 You could not be verified. Please try again...

감독자! 예상대로 작동하므로 이제 실제 사용자 이름과 비밀번호를 사용해 보겠습니다.

 user: Fluffeh pass: mypass

놀라운! 모든 라운드에서 하이파이브 코드는 관리자를 올바르게 확인했습니다. 그것은 완벽!

글쎄,별로. 사용자가 영리한 작은 사람이라고 가정해 보겠습니다. 그 사람이 나라고 하자.

다음을 입력합니다.

 user: bob pass: n' or 1=1 or 'm=m

출력은 다음과 같습니다.

 The check passed. We have a verified admin!

축하합니다. 제가 사용자 이름과 비밀번호를 잘못 입력한 상태에서 제가 최고로 보호되는 관리자 전용 섹션에 들어가는 것을 허용했습니다. 진심으로, 내 말을 믿지 않는다면 내가 제공한 코드로 데이터베이스를 만들고 이 PHP 코드를 실행하세요.

그래서, 대답으로, 그것이 당신이 고함을 받는 이유입니다.

그럼 무엇이 잘못되었는지, 그리고 내가 왜 당신의 최고 관리자 전용 박쥐 동굴에 들어갔는지 살펴보겠습니다. 나는 추측을 했고 당신이 입력에 주의하지 않고 단순히 데이터베이스에 직접 전달했다고 가정했습니다. 실제로 실행 중인 쿼리를 변경하는 방식으로 입력을 구성했습니다. 그래서, 그것은 무엇이 되어야 했고, 결국 무엇이 되었습니까?

 select id, userid, pass from users where userid='$user' and pass='$pass'

그것이 쿼리이지만 변수를 우리가 사용한 실제 입력으로 바꾸면 다음을 얻습니다.

 select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

먼저 암호 주위에 작은 따옴표를 닫고 완전히 새로운 비교를 도입하도록 "비밀번호"를 구성한 방법을 참조하십시오. 그런 다음 안전을 위해 원래 코드에서 작은 따옴표가 예상대로 닫히도록 다른 "문자열"을 추가했습니다.

그러나 이것은 지금 당신에게 소리치는 사람들에 대한 것이 아니라 코드를 더 안전하게 만드는 방법을 보여주기 위한 것입니다.

자, 무엇이 잘못되었고 어떻게 고칠 수 있습니까?

이것은 고전적인 SQL 주입 공격입니다. 그 문제에 대한 가장 간단한 것 중 하나입니다. 공격 벡터의 규모에서 이것은 탱크를 공격하고 승리하는 유아입니다.

그렇다면 신성한 관리 섹션을 어떻게 보호하고 멋지고 안전하게 만들 수 있습니까? 가장 먼저 할 일은 정말 오래되고 더 이상 사용되지 않는 mysql_* 함수의 사용을 중단하는 것입니다. 나는 당신이 온라인에서 찾은 튜토리얼을 따랐고 작동하지만 오래되고 구식이며 몇 분 만에 땀을 흘리지 않고 그냥 지나쳤습니다.

이제 mysqli_ 또는 PDO 를 사용하는 더 나은 옵션이 있습니다. 저는 개인적으로 PDO의 열렬한 팬이므로 이 답변의 나머지 부분에서 PDO를 사용할 것입니다. 장점과 단점이 있지만 개인적으로 장점이 단점보다 훨씬 크다고 생각합니다. MySQL을 사용하든 Oracle을 사용하든 아니면 거의 모든 것을 사용하든 상관없이 여러 데이터베이스 엔진에서 이식 가능합니다. 나는 깨끗한 것을 좋아한다.

이제 PDO 개체를 사용하여 작성된 코드를 다시 살펴보겠습니다.

 <?php if(!empty($_POST['user'])) { $user=$_POST['user']; } else { $user='bob'; } if(!empty($_POST['pass'])) { $pass=$_POST['pass']; } else { $pass='bob'; } $isAdmin=false; $database='prep'; $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example'); $sql="select id, userid, pass from users where userid=:user and pass=:password"; $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); if($myPDO->execute(array(':user' => $user, ':password' => $pass))) { while($row=$myPDO->fetch(PDO::FETCH_ASSOC)) { echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>"; $isAdmin=true; // We have correctly matched the Username and Password // Lets give this person full access } } if($isAdmin) { echo "The check passed. We have a verified admin!<br>"; } else { echo "You could not be verified. Please try again...<br>"; } ?> <form name="exploited" method='post'> User: <input type='text' name='user'><br> Pass: <input type='text' name='pass'><br> <input type='submit'> </form>

주요 차이점은 더 이상 mysql_* 함수가 없다는 것입니다. 두 번째로 준비된 명령문을 사용하는 PDO 개체를 통해 모두 수행됩니다. 자, 당신이 묻는 사전 진술은 무엇입니까? 쿼리를 실행하기 전에 데이터베이스에 우리가 실행할 쿼리가 무엇인지 알려주는 방법입니다. 이 경우 우리는 데이터베이스에 "안녕하세요, userid가 변수이고 pass도 변수인 테이블 users에서 id, userid 및 pass를 원하는 select 문을 실행할 것입니다."라고 말합니다.

그런 다음 실행 문에서 현재 예상하는 모든 변수가 포함된 데이터베이스 배열을 전달합니다.

결과는 환상적입니다. 이전의 사용자 이름과 비밀번호 조합을 다시 시도해 보겠습니다.

 user: bob pass: somePass

사용자가 확인되지 않았습니다. 엄청난.

어때요:

 user: Fluffeh pass: mypass

오, 방금 약간 흥분했습니다. 작동했습니다. 수표가 통과했습니다. 확인된 관리자가 있습니다!

이제 영리한 친구가 우리의 작은 검증 시스템을 통과하기 위해 입력할 데이터를 사용해 보겠습니다.

 user: bob pass: n' or 1=1 or 'm=m

이번에는 다음을 얻습니다.

 You could not be verified. Please try again...

이것이 질문을 게시할 때 소리를 지르는 이유입니다. 사람들이 시도하지 않고도 귀하의 코드를 우회할 수 있다는 것을 알 수 있기 때문입니다. 이 질문과 답변을 사용하여 코드를 개선하고 더 안전하게 만들고 최신 기능을 사용하십시오.

마지막으로 이것이 완벽한 코드라는 말은 아닙니다. 이를 개선하기 위해 할 수 있는 일이 더 많습니다. 예를 들어 해시된 암호를 사용하고, 민감한 정보를 데이터베이스에 저장할 때 일반 텍스트로 저장하지 않고, 여러 수준의 검증을 거치도록 합니다. 주입하기 쉬운 이전 코드를 이것으로 변경하면 좋은 코드를 작성하는 데 도움이 될 것입니다. 여기까지 왔고 여전히 읽고 있다는 사실은 이 유형을 구현할 뿐만 아니라 희망적인 느낌을 줍니다. 웹사이트와 응용 프로그램을 작성할 때 코드를 사용하지 않고 나가서 방금 언급한 다른 것들을 조사할 수 있습니다. 거의 작동하지 않는 가장 기본적인 코드가 아니라 가능한 최고의 코드를 작성하십시오.


Fluffeh

MySQL 확장은 세 가지 중 가장 오래된 것으로 개발자가 MySQL과 통신하는 데 사용하는 원래 방식이었습니다. 이 확장은 이제 PHP와 MySQL의 최신 릴리스에서 개선된 사항으로 인해 다른 두 가지 대안 을 위해 더 이상 사용되지 않습니다.

  • MySQLi 는 MySQL 데이터베이스 작업을 위한 '개선된' 확장입니다. 최신 버전의 MySQL 서버에서 사용할 수 있는 기능을 활용하고 개발자에게 기능 지향 및 객체 지향 인터페이스를 모두 노출하며 다른 몇 가지 멋진 작업을 수행합니다.

  • PDO 는 이전에 주요 데이터베이스 액세스 확장(예: MySQL, PostgreSQL, SQLite, MSSQL 등)에 분산되어 있던 대부분의 기능을 통합하는 API를 제공합니다. 인터페이스는 프로그래머가 데이터베이스 연결, 쿼리 및 결과 집합 및 하위 수준 드라이버는 데이터베이스 서버와 통신 및 리소스 처리를 수행합니다. 많은 토론과 작업이 PDO에 진행 중이며 현대적이고 전문적인 코드에서 데이터베이스를 사용하는 적절한 방법으로 간주됩니다.


Alexander

위의 답변이 정말 길어 요약하자면 다음과 같습니다.

mysqli 확장에는 많은 이점이 있으며 mysql 확장에 대한 주요 개선 사항은 다음과 같습니다.

  • 객체 지향 인터페이스
  • 준비된 진술 지원
  • 다중 명령문 지원
  • 거래 지원
  • 향상된 디버깅 기능
  • 임베디드 서버 지원

출처: MySQLi 개요


위의 답변에서 설명했듯이 mysql의 대안은 mysqli와 PDO(PHP Data Objects)입니다.

  • API는 서버 측 준비된 명령문을 지원합니다: MYSQLi 및 PDO에서 지원
  • API는 클라이언트 측 준비된 문 지원: PDO에서만 지원
  • API는 저장 프로시저를 지원합니다: MySQLi 및 PDO 모두
  • API는 다중 명령문 및 모든 MySQL 4.1+ 기능을 지원합니다. - MySQLi 및 대부분 PDO에서도 지원됩니다.

MySQLi와 PDO는 모두 PHP 5.0에서 도입된 반면 MySQL은 PHP 3.0 이전에 도입되었습니다. 주의할 점은 MySQL이 PHP5.x에 포함되어 있지만 이후 버전에서는 더 이상 사용되지 않는다는 것입니다.


Ani Menon

mysqli 또는 PDO를 사용하여 mysql_* 함수를 정의하는 것이 가능합니다. 이전 PHP 애플리케이션 위에 포함하기만 하면 PHP7에서 작동합니다. 여기 내 솔루션 .

 <?php define('MYSQL_LINK', 'dbl'); $GLOBALS[MYSQL_LINK] = null; function mysql_link($link=null) { return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link; } function mysql_connect($host, $user, $pass) { $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass); return $GLOBALS[MYSQL_LINK]; } function mysql_pconnect($host, $user, $pass) { return mysql_connect($host, $user, $pass); } function mysql_select_db($db, $link=null) { $link = mysql_link($link); return mysqli_select_db($link, $db); } function mysql_close($link=null) { $link = mysql_link($link); return mysqli_close($link); } function mysql_error($link=null) { $link = mysql_link($link); return mysqli_error($link); } function mysql_errno($link=null) { $link = mysql_link($link); return mysqli_errno($link); } function mysql_ping($link=null) { $link = mysql_link($link); return mysqli_ping($link); } function mysql_stat($link=null) { $link = mysql_link($link); return mysqli_stat($link); } function mysql_affected_rows($link=null) { $link = mysql_link($link); return mysqli_affected_rows($link); } function mysql_client_encoding($link=null) { $link = mysql_link($link); return mysqli_character_set_name($link); } function mysql_thread_id($link=null) { $link = mysql_link($link); return mysqli_thread_id($link); } function mysql_escape_string($string) { return mysql_real_escape_string($string); } function mysql_real_escape_string($string, $link=null) { $link = mysql_link($link); return mysqli_real_escape_string($link, $string); } function mysql_query($sql, $link=null) { $link = mysql_link($link); return mysqli_query($link, $sql); } function mysql_unbuffered_query($sql, $link=null) { $link = mysql_link($link); return mysqli_query($link, $sql, MYSQLI_USE_RESULT); } function mysql_set_charset($charset, $link=null){ $link = mysql_link($link); return mysqli_set_charset($link, $charset); } function mysql_get_host_info($link=null) { $link = mysql_link($link); return mysqli_get_host_info($link); } function mysql_get_proto_info($link=null) { $link = mysql_link($link); return mysqli_get_proto_info($link); } function mysql_get_server_info($link=null) { $link = mysql_link($link); return mysqli_get_server_info($link); } function mysql_info($link=null) { $link = mysql_link($link); return mysqli_info($link); } function mysql_get_client_info() { $link = mysql_link(); return mysqli_get_client_info($link); } function mysql_create_db($db, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); return mysqli_query($link, "CREATE DATABASE `$db`"); } function mysql_drop_db($db, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); return mysqli_query($link, "DROP DATABASE `$db`"); } function mysql_list_dbs($link=null) { $link = mysql_link($link); return mysqli_query($link, "SHOW DATABASES"); } function mysql_list_fields($db, $table, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); $table = str_replace('`', '', mysqli_real_escape_string($link, $table)); return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`"); } function mysql_list_tables($db, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); return mysqli_query($link, "SHOW TABLES FROM `$db`"); } function mysql_db_query($db, $sql, $link=null) { $link = mysql_link($link); mysqli_select_db($link, $db); return mysqli_query($link, $sql); } function mysql_fetch_row($qlink) { return mysqli_fetch_row($qlink); } function mysql_fetch_assoc($qlink) { return mysqli_fetch_assoc($qlink); } function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) { return mysqli_fetch_array($qlink, $result); } function mysql_fetch_lengths($qlink) { return mysqli_fetch_lengths($qlink); } function mysql_insert_id($qlink) { return mysqli_insert_id($qlink); } function mysql_num_rows($qlink) { return mysqli_num_rows($qlink); } function mysql_num_fields($qlink) { return mysqli_num_fields($qlink); } function mysql_data_seek($qlink, $row) { return mysqli_data_seek($qlink, $row); } function mysql_field_seek($qlink, $offset) { return mysqli_field_seek($qlink, $offset); } function mysql_fetch_object($qlink, $class="stdClass", array $params=null) { return ($params === null) ? mysqli_fetch_object($qlink, $class) : mysqli_fetch_object($qlink, $class, $params); } function mysql_db_name($qlink, $row, $field='Database') { mysqli_data_seek($qlink, $row); $db = mysqli_fetch_assoc($qlink); return $db[$field]; } function mysql_fetch_field($qlink, $offset=null) { if ($offset !== null) mysqli_field_seek($qlink, $offset); return mysqli_fetch_field($qlink); } function mysql_result($qlink, $offset, $field=0) { if ($offset !== null) mysqli_field_seek($qlink, $offset); $row = mysqli_fetch_array($qlink); return (!is_array($row) || !isset($row[$field])) ? false : $row[$field]; } function mysql_field_len($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset); return is_object($field) ? $field->length : false; } function mysql_field_name($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset); if (!is_object($field)) return false; return empty($field->orgname) ? $field->name : $field->orgname; } function mysql_field_table($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset); if (!is_object($field)) return false; return empty($field->orgtable) ? $field->table : $field->orgtable; } function mysql_field_type($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset); return is_object($field) ? $field->type : false; } function mysql_free_result($qlink) { try { mysqli_free_result($qlink); } catch (Exception $e) { return false; } return true; }

Pavel Tzonkov

출처 : http:www.stackoverflow.com/questions/12859942/why-shouldnt-i-use-mysql-functions-in-php

반응형