Help us understand the problem. What is going on with this article?

SQLから任意のシェルをキックする(MySQL)

More than 3 years have passed since last update.

バッチ系システムの設計で
cron から定期実行
だけだとちょっとタイミング的に厳しいかも?
ということがあり
調べていたところSQLからシェルをキックできることが判明!

User Define Function
の頭文字からUDFと呼ばれてるものを使用すればイケるみたい
ということがわかったので
早速
どんなものなのかを試してみました。

そのときのメモを載せておきます。

環境

MySQLが載ってるサーバです。

# cat /etc/issue
CentOS release 6.8 (Final)
Kernel \r on an \m

MySQLです。

mysql> SELECT version();
+------------+
| version()  |
+------------+
| 5.7.12-log |
+------------+
1 row in set (0.16 sec)

流れ的なもの

MySQLに自作のファンクションを作成し、登録する。
そのファンクションからシェルをキックするという流れです。

自作するファンクションはストアド感覚な感じですが
c++で作成したプログラムを登録することが可能。

cにはsystem関数という実行環境のコマンドをキックできる関数があるので
それを利用するという感じになります。

ということで、
まずはc言語のソースを。。
コピペでOKです。

exec.cpp
#include <mysql.h>
#include <m_string.h>

extern "C" {
    my_bool exec_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
    my_ulonglong exec(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);
    void exec_deinit(UDF_INIT *initid);
}

// init
my_bool exec_init(UDF_INIT *initid, UDF_ARGS *args, char *message){
    if(args->arg_count == 1 && args->arg_type[0]==STRING_RESULT){
        return 0;
    } else {
        strcpy(message, "Expected exactly one string type parameter" );
        return 1;
    }
}

my_ulonglong exec(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error){
    return system(args->args[0]);
}

void exec_deinit(UDF_INIT *initid){
    // do nothing
}

次にこれをコンパイル。
ということでコンパイラーをインストール。
自分は以下のものを入れました。

  • gcc-c++-4.4.7-17.el6.x86_64.rpm
  • libstdc++-devel-4.4.7-17.el6.x86_64.rpm

yumでもokだと思います。
自分の場合、このサーバは外部につながってなかったのでrpmになりました・・・

コンパイラを入れたら上記ソースをコンパイルしてみます。

g++ -Wall -I/usr/include/mysql -shared -o exec.so -fPIC exec.cpp

成功すると exec.so がカレントに出力されるのでこれをmysqlのpluginにコピーする

cp exec.so /usr/lib64/mysql/plugin

あとはMySQLからcreate functionすれば完成です。

mysql> CREATE FUNCTION exec RETURNS int SONAME 'exec.so';

レプリケーションしている場合は要注意です!

実行

早速実行するため適当なシェルを用意する

# cat /work/catt.sh
#!/bin/sh
ls -al /work > /tmp/catt

で、実行。

mysql> select exec("/work/catt.sh");
+-----------------------+
| exec("/work/catt.sh") |
+-----------------------+
| 32256 |
+-----------------------+
1 row in set (0.01 sec)

あれ?戻り値おかしいぞ?
/tmp を覗いてもファイルがないし。。

念のため
/var/log/mysqld.logを覗いてみる。

sh: /work/catt.sh: 許可がありません

え?シェルの実行権限あるよ!
一応chmodで777にしてみたけど結果は変わらず・・・

・・・ここでハマる事、数時間。




原因はコレでした

# getenforce
Enforcing

selinuxが有効だった・・・(ubuntu でいうところの apparmor)

ということでこれを解除します。

# setenforce 0
# getenforce
Permissive

この後、/etc/sysconfig/selinux を編集。
SELINUX=の右辺をdisabledに修正後、サーバーをrebootする。

ちと自分の場合、諸事理由ですぐにはrebootできなかったので
Permissive
のまま続行~

mysql> select exec("/work/catt.sh");
+-----------------------+
| exec("/work/catt.sh") |
+-----------------------+
| 0 |
+-----------------------+
1 row in set (0.02 sec)

お、成功か?

# ll /tmp/catt
-rw-r-----. 1 mysql mysql 234 10月 24 16:18 2017 catt

ファイル出力できてました。成功です。

最後に

シェルをキックするのはこれでいけますが
シェルをキックして、その戻り値をselectでそのまま表示
というのはコレではできないので
キックしたシェルからDBに結果を書き込むなどの処理が必要になってきます。

この調査途中でシェルの結果を得るのもできそうだったので
今度時間あるときに調べてみようかと思います。

以上ご参考になれば。

n_a
java屋です。 でもここでは備忘録という感じで java以外のことをメインに書いていきたいです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away