programing

PHP에서 SSH를 통해 MySQL 서버에 연결

lovejava 2023. 10. 21. 09:47

PHP에서 SSH를 통해 MySQL 서버에 연결

원격 리눅스 머신에 데이터베이스를 가지고 있는데 SSH와 PHP 기능을 이용해서 접속하고 싶습니다(현재는 SSH2 라이브러리를 사용하고 있습니다).mysql_connect를 사용하려고 했지만 이 기능을 사용하려고 하면 액세스할 수 없습니다(권한은 부여했지만).

$connection = ssh2_connect('SERVER IP', 22);

ssh2_auth_password($connection, 'username', 'password');

$tunnel = ssh2_tunnel($connection, 'DESTINATION IP', 3307);

$db = mysqli_connect('127.0.0.1', 'DB_USERNAME', 'DB_PASSWORD', 
                         'dbname', 3307, $tunnel)
    or die ('Fail: '.mysql_error());

"mysqli_connect()에서 매개 변수 6을 문자열로 예상합니다, 리소스가 제공됩니다."라는 오류가 발생했습니다.어떻게 해결할 수 있습니까?

SSH 터널 솔루션

보안을 위해 Jumpbox 프록시를 통해 MySQL 데이터베이스 서버에 SSH 터널을 설정합니다.


(A) GUI 도구

요구 사항에 따라 Visual Studio Code Forwarding a Port, TablePlus같이 SSH Tunneling이 내장된 GUI MySQL 클라이언트를 사용하거나 PuTTY를 사용하여 로컬 포트 포워딩을 설정할 수 있습니다.

macOS에서는 Secure PipesTablePlus를 좋아합니다.


(B) 명령줄

1단계.

ssh -fNg -L 3307:10.3.1.55:3306 username@ssh-jumpbox.com 

여기서 핵심은 ssh에 로컬 포트 포워딩을 요청한다는 '-L' 스위치입니다.

위의 3307번 포트를 사용하기로 했습니다.이 포트로 향하는 로컬 시스템의 모든 트래픽이 이제 ssh 클라이언트를 통해 주소의 호스트에서 실행 중인 ssh 서버로 '포트 전달'됩니다.ssh-jumpbox.com.

Jumpbox ssh 프록시 서버가 트래픽을 해독하고 대신 MySQL 데이터베이스 서버에 대한 네트워크 연결을 설정합니다.10.3.1.55:3306에는 MySQL 데이터베이스 서버는 Jumpbox의 내부 네트워크에서 들어오는 을 봅니다.MySQL 데이터베이스 서버는 Jumpbox의 내부 네트워크 주소에서 들어오는 연결을 봅니다.


IMT2000 3GPP - 로컬 포트 포워딩 구문
구문은 약간 까다롭지만 다음과 같이 볼 수 있습니다.

<local_workstation_port>:<database_server_addr_remote_end_of_tunnel>:<database_server_port_remote_end> username@ssh_proxy_host.com

다른 스위치에 관심이 있다면 다음과 같습니다.

f()
안 함N함)
-g (원격 호스트가 로컬 전달 포트에 연결할 수 있도록 허용)

개인 키 인증, 위에 (-i) 스위치 추가:

-i/path/to/private-key

2단계.

로컬 MySQL 클라이언트에게 시스템의 로컬 포트 3307(-h 127.0.0.0.1)을 통해 SSH 터널을 통해 연결하고, 이제 1단계에서 설정한 SSH 터널을 통해 전송된 모든 트래픽을 전달하도록 지시합니다.

mysql -h 127.0.0.1 -P 3307 -u dbuser -p passphrase

이제 클라이언트와 서버 간의 데이터 교환은 암호화된 SSH 연결을 통해 전송되며 안전합니다.


보안노트
데이터베이스 서버에 직접 터널링하지 마십시오.인터넷에서 직접 접근할 수 있는 데이터베이스 서버를 갖추는 것은 엄청난 보안 부담입니다.터널 대상 주소를 Jumpbox/Bastion Host의 인터넷 주소(1단계 예 참조)로 만들고 데이터베이스 대상을 원격 네트워크에 있는 데이터베이스 서버의 내부 IP 주소로 만듭니다.나머지는 SSH가 합니다.


3단계.

이제 PHP 애플리케이션을 다음과 연결합니다.

<?php
      $smysql = mysql_connect( "127.0.0.1:3307", "dbuser", "passphrase" );
      mysql_select_db( "db", $smysql ); 
?>

MySQL 연결을 위한 ssh 명령줄 터널링에 대해 자세히 설명한 Chris Snyder의 훌륭한 기사에 감사드립니다.

불행히도 php가 제공하는 ssh2 터널은 로컬 포트를 터널로 지정할 수 없기 때문에 원격 mysql 연결을 처리할 수 없는 것 같습니다(22번 포트 또는 원격 서버가 실행 중인 어떤 ssh 포트에서만 작동합니다.이에 대한 제 해결책은 다음을 통해 터널을 여는 것입니다.exec()오퍼레이터와 평소와 같이 연결합니다.

exec('ssh -f -L 3307:127.0.0.1:3306 user@example.com sleep 10 > /dev/null');
$mysqli = new mysqli('127.0.0.1', 'user', 'password', 'database', '3307');

저도 같은 것을 찾고 있었지만, 외부 명령이 필요하지 않고 외부 프로세스를 관리하는 것을 선호합니다.그래서 어느 순간, 어떤 PHP 스트림에서도 작동할 수 있는 순수한 PHP MySQL 클라이언트를 작성하는 것이 얼마나 어려울 수 있을까 하는 생각이 들었습니다.MySQL 프로토콜 문서를 기반으로 했을 때 반나절 정도 걸렸습니다.

https://gist.github.com/UCIS/4e509915ed221660e58f5169267da004

SSH2 라이브러리 또는 다른 스트림과 함께 사용할 수 있습니다.

    $ssh = ssh2_connect('ssh.host.com');
    ssh2_auth_password($ssh, 'username', 'password');
    $stream = ssh2_tunnel($ssh, 'localhost', 3306);
    $link = new MysqlStreamDriver($stream, 'SQLusername', 'SQLpassword', 'database');
    $link->query('SELECT * FROM ...')->fetch_assoc();

완전한 mysqli API를 구현하지는 않지만 모든 평문 질의와 함께 작동해야 합니다.이것을 사용하기로 결정하셨다면 조심해주세요, 아직 코드 테스트를 충분히 하지 않았고 문자열 탈출 코드도 검토하지 않았습니다.

대부분의 사용자가 이 작업을 수행하는 데 문제가 있는 이유는 mysql 서버의 사용자가 localhost의 IP 주소인 127.0.0.1이 아닌 "localhost"에서만 허용하도록 설정되어 있기 때문이라고 생각합니다.

다음 단계를 수행하여 이 작업을 수행할 수 있습니다.

1단계: 대상 사용자에게 127.0.0.1 호스트 허용

SSH는 일반적으로 서버에 로그인한 후 mysql root 사용자로 로그인한 후 다음 명령을 실행합니다.

GRANT ALL ON yourdbname.* TO yourdbuser@127.0.0.1 IDENTIFIED BY 'yourdbpassword';

물론 위의 127.0.0.1을 지정하는 것이 핵심입니다.

2단계: MySQL에 대한 로컬 SSH 터널 시작

이제 원격 MySQL 서버에 대한 로컬 SSH 터널을 다음과 같이 시작할 수 있습니다.

ssh -vNg -L 33306:127.0.0.1:3306 sshuser@remotehost.com

-vssh를 장황한 모드로 작동하게 해서 무슨 일이 일어나고 있는지 확인하는 데 도움이 됩니다.예를 들어 연결을 시도할 때 터미널 콘솔에 다음과 같은 디버깅 출력이 표시됩니다.

debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Connection to port 33306 forwarding to 127.0.0.1 port 3306 requested.

연결을 닫으면 다음과 같이 출력됩니다.

debug2: channel 2: is dead
debug2: channel 2: garbage collecting
debug1: channel 2: free: direct-tcpip: listening port 33306 for 127.0.0.1 port 3306, connect from 127.0.0.1 port 52112 to 127.0.0.1 port 33306, nchannels 3

-Nssh를 명령어를 발행하지 않고 연결을 설정한 후에 그냥 기다립니다.

-g원격 호스트에서 로컬 전달 포트에 연결할 수 있습니다.이것이 필요한지 확실하지 않지만 동일한 SSH 터널을 통해 여러 연결을 다중화하는 데 유용할 수 있습니다.

-L이것은 로컬 포트를 지정하는 주요 매개 변수입니다.33306원격 호스트의 로컬 IP 주소에 연결합니다.127.0.0.1그리고 원격 호스트의 mysql 포트는 보통3306.

이 후 필요한 모든 메커니즘/기타 매개 변수를 사용하여 SSH를 통해 원격 호스트에 연결할 수 있습니다.내 경우에는 내 컴퓨터에 구성된 키 파일을 사용합니다.~/.ssh/config그래서 난 단지 특정할 필요가 있습니다.user@host들어가기 위해.

이렇게 명령을 내리면 포그라운드에서 SSH가 실행되므로 쉽게 닫을 수 있습니다.Ctrl + C. 백그라운드 프로세스에서 이 터널을 실행하려면 추가할 수 있습니다.-f이렇게 하기 위해서.

3단계: PHP/기타 mysql 호환 메서드에서 연결

로컬 호스트의 위에서 실행 중인 SSH 터널은 마치 mysql이 실행 중인 것과 동일하게 동작합니다.127.0.0.1. 포트를 사용합니다.33306(triple 3 참조) 일반 포트에서 로컬 SQL 서버를 실행할 수 있습니다.이제 평소에 하던 대로 연결할 수 있습니다. 그.mysql터미널의 명령어는 다음과 같습니다.

mysql -h 127.0.0.1 -P 33306 -u yourmysqluser -p

어디에-P(capital P)는 SSH 터널의 로컬 끝이 연결을 수락하는 포트를 지정합니다.사용하는 것이 중요합니다.127.0.0.1신 IP 대신 localhost왜냐하면 mysql cli는 아마도 리눅스 소켓을 사용하여 연결하려고 시도할 것이기 때문입니다.

PHP 연결 문자열의 경우 데이터 원본 이름 문자열(Yii2 구성의 경우)은 다음과 같습니다.

'dsn' => 'mysql:host=127.0.0.1;dbname=yourdbname;port=33306',

암호 및 사용자 이름은 일반으로 지정됩니다.

문서에 따르면 마지막 매개 변수는 '/var/run/mysql/mysql과 같은 소켓 또는 파이프 이름이어야 합니다.당신은 UNIX 소켓을 사용하여 연결하는 것이 아니기 때문에 당신에게는 해당되지 않습니다.그러니 그냥 빼놓도록 해보세요.

루트 자격 증명과 공개 개인 키 쌍 모두에서 ssh를 수행하여 시도했지만 명령줄을 통해 연결할 수 있지만 php 코드를 통해 연결할 수는 없습니다.터널도 만들어서(ssh2 기능을 사용해서) 시도했고, php 코드(시스템, exec 등)에서 셸 명령을 실행했지만 아무 것도 작동하지 않았습니다.드디어 shell command를 실행하기 위해 ssh2 기능을 시도해보았는데 드디어 작동하였습니다 :) 여기 코드가 있습니다, 만약 도움이 된다면:----

$connection = ssh2_connect($remotehost, '22');
if (ssh2_auth_password($connection, $user,$pass)) { 
    echo "Authentication Successful!\n";
} else {
    die('Authentication Failed...');
}

$stream=ssh2_exec($connection,'echo "select * from zingaya.users where id=\"1606\";" | mysql');
stream_set_blocking($stream, true);
while($line = fgets($stream)) { 
    flush();
    echo $line."\n";
}

특별히 php 기능을 사용하고 싶다면 이것을 시도해 볼 수 있었습니다.

연결 중인 사용자 이름과 암호에 올바른 호스트 이름 권한이 있는지 확인합니다.와일드카드는 '%'를 사용할 수 있다고 생각합니다.또한 로컬 네트워크에 없는 원격 컴퓨터에 연결하는 경우(Ssh into the local network 상에 있는 원격 컴퓨터에 연결하는 경우), 외부 트래픽을 위한 서버가 있는 라우터의 포트를 전달해야 연결할 수 있습니다.

http://www.lanexa.net/2011/08/create-a-mysql-database-username-password-and-permissions-from-the-command-line/

ssh2를 사용하여 연결할 때, php가 ssh2를 사용하여 mysql에 연결할 수 있는 특별한 기능이 없기 때문에, 우리는 데이터 상호 작용을 위해 전통적인 socket과 mysql 프로토콜만 사용할 수 있습니다.

우리가 사용할 때.ssh2_tunnelmethod, 생성이 성공하면 socket 개체가 반환됩니다. 그러면 이 socket 개체를 mysql 데이터 상호 작용에 사용할 수 있습니다.물론 우리는 mysql 프로토콜, 이른바 "악수 패킷"을 이해할 필요가 있습니다.

......
$tunnel = ssh2_tunnel(....)
......

//we need to construct the mysql data packet and then use fwrite to transfer this packet to mysql host

fwrite($tunnel, MYSQLDATAPACKET)

ssh2_connect 메서드를 사용하여 mysql 호스트를 연결하면 MySQL 핸드셰이크 패킷이 반환되며 다음과 같습니다.

a 5.5.5-10.3.34-MariaDB-cll-lve�WfyP`uKW����RtscuF:/}J7umysql_native_password!��

다음은 mysql 핸드셰이크 패킷 구조입니다.

size(byte)  description
1           protocol version
n           server version
4           connection id
8           auth-plugin-data-part-1
1           filler
2           capability flags
1           character set
2           status flags
2           capability flags
1           length of auth-plugin-data
10          reserved
13          auth-plugin-data-part-2
n           auth-plugin name

그래서 우리는 그러한 구조를 사용하여 바이트 배열을 파싱해야 합니다.

Mysql handshake 를 받은 후, 우리는 mysql host 를 연결했고, 이제 mysql 로그인을 해야하기 때문에 mysql send data 패킷을 구성해야 합니다.

데이터 구조: (Handshake Response41)

4              capability flags, CLIENT_PROTOCOL_41 always set
4              max-packet size
1              character set
string[23]     reserved (all [0])
string[NUL]    username
  if capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA {
lenenc-int     length of auth-response
string[n]      auth-response
  } else if capabilities & CLIENT_SECURE_CONNECTION {
1              length of auth-response
string[n]      auth-response
  } else {
string[NUL]    auth-response
  }
  if capabilities & CLIENT_CONNECT_WITH_DB {
string[NUL]    database
  }
  if capabilities & CLIENT_PLUGIN_AUTH {
string[NUL]    auth plugin name
  }
  if capabilities & CLIENT_CONNECT_ATTRS {
lenenc-int     length of all key-values
lenenc-str     key
lenenc-str     value
   if-more data in 'length of all key-values', more keys and value pairs
  }

참고 항목: https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol :: 악수 응답41

좋아요, 그럼 mysql 호스트를 성공적으로 연결했고 데이터베이스를 조회할 수 있습니다.

다음은 Mysql 핸드셰이크 및 Mysql 소켓 로그인의 샘플 코드입니다.

function parseMysqlHandshakePack($hex_string)
{
    $dataField = {
        "protocol_version" => "",
        "server_version" => "",
        "thread_id" => "",
        "salt1" => "",
        "salt2" => "",
        "salt" => "",
    }
    
    $dataField["protocol_version"] = UtiliHelper::HexToInt(UtiliHelper::HexSub($hex_string,0,1));
    $dataField["server_version"] = UtiliHelper::HexToStr(UtiliHelper::HexSub($hex_string,1,7));
    $dataField["thread_id"] = UtiliHelper::HexToInt(UtiliHelper::HexSub($hex_string,8,4));
    $dataField["salt1"] = UtiliHelper::HexSub($hex_string,12,8);
    $dataField["salt2"] = UtiliHelper::HexSub($hex_string,39,12);
    
    $dataField["salt"] = $dataField["salt1"] . $dataField["salt2"];
    
    return $dataField;
}
function constructMysqlLoginPacket($username, $password, $database, $salt){
    $tags = [
        "power_tag" => "",
        "power_ext" => "",
        "max_length" => "",
        "charset" => "",
        "fill_pad" => "",
        "username" => "",
        "password" => "",
        "database" => "",
        "client_auth_plugin" => "",
        "payload" => ""
    ];
    $tags['power_tag'] = "8da2";
    $tags['power_ext'] = "0b00";
    $tags['max_length'] = "000000c0";
    $tags['charset'] = "08";
    $tags['fill_pad'] = "0000000000000000000000000000000000000000000000";
    $tags['client_auth_plugin'] = "6d7973716c5f6e61746976655f70617373776f726400";
    $tags["payload"] = "150c5f636c69656e745f6e616d65076d7973716c6e64";
    
    $tags['username'] = UtiliHelper::StrToHex($username)."0014";
    $tags['password'] = UtiliHelper::encryptionPass($password,$salt);
    $tags['database'] = UtiliHelper::StrToHex($database)."00";
    
    $message = "";
    foreach ($tags as $tagv){
        $message .= $tagv;
    }
    return UtiliHelper::IntToHex(strlen($message)/2)."01".$message;
}

$salt파라미터는 핸드셰이크 단계에서 나왔기 때문에, 당신은 mysql 핸드셰이크 메시지를 파싱하고 그 메시지를 받아야 합니다.salt.

UtiliHelper는 Github: https://github.com/gphper/PHPMysql/blob/master/src/UtiliHelper.php 의 서드파티 프로젝트에서 제공되는 사용자 정의 클래스입니다.

언급URL : https://stackoverflow.com/questions/464317/connect-to-a-mysql-server-over-ssh-in-php