通过Bash脚本转义MYSQL命令行

19

PHP有mysql_real_escape_string()函数来正确转义可能会引起问题的任何字符。如何在BASH中模仿这个功能呢?

是否有办法使用BASH进行预处理的mysql语句?这似乎是最好的方法。

我的大多数变量不会(也不应该)具有特殊字符,但我允许用户完全自由地选择密码。它可能包含像'和"这样的字符。

我可能会执行多个SQL语句,所以我想编写一个脚本,接受参数然后运行语句。这是我到目前为止写的:

doSQL.sh:

#!/bin/sh

SQLUSER="root"
SQLPASS="passwor339c"
SQLHOST="localhost"

SQL="$1"
SQLDB="$2"


if [ -z "$SQL" ]; then echo "ERROR: SQL not defined"; exit 1; fi
if [ -z "$SQLDB" ]; then SQLDB="records"; fi

echo "$SQL" | mysql -u$SQLUSER -p$SQLPASS -h$SQLHOST $SQLDB

还有一个使用该命令的例子:

example.sh:

PASSWORD=$1
doSQL "INSERT INTO active_records (password) VALUES ('$PASSWORD')"

如果密码中包含单引号,显然这将失败。

9个回答

17

在Bash中,printf可以为您进行转义:

$ a=''\''"\;:#[]{}()|&^$@!?, .<>abc123'
$ printf -v var "%q" "$a"
$ echo "$var"
\'\"\\\;:#\[\]\{\}\(\)\|\&\^\$@\!\?\,\ .\<\>abc123

我会让你自己决定是否够强硬。


当在变量a中给出$#时,似乎会失败。 - ParoX
@BHare:对我来说它是有效的:a='$#'; printf ...; echo ... 给了我 \$#。你得到了什么?除非你告诉我,否则我无法判断它为何“似乎失败”。 - Dennis Williamson
我意识到它是在插值$#,因为我正在使用双引号。 - ParoX
3
当尝试转义UTF8字符时,你可能会遇到问题。比如,UTF   可能会被转义为 \302240,这可能会给你带来很多麻烦。请注意处理。 - Stefan Rogin
如果你面临的问题是在之前添加了一个 "$",那么很容易通过以下方式解决: printf -v CMD_ESCAPED %q "$CMD_RESULT" CMD_ESCAPED="${CMD_ESCAPED:1}"之后它就可以正常工作了。 - OnTheFly
当转义utf8字符时,您可能会遇到问题。我不认为这是真的。我测试了mysql ... -e 'select * from job where name = '"'$(printf "%q" "$(printf "\ua0")")'" db并且它可以工作,但我不确定与“连接/数据库字符集”有关的任何信息。 - KamilCuk

6
无论你使用何种引号,以下结构都是无法避免的:

function quoteSQL() {
    printf "FROM_BASE64('%s')" "$(echo -n "$1" | base64 -w0 )"
}

PASSWORD=$1
doSQL "INSERT INTO active_records (password) VALUES ($(quoteSQL "$PASSWORD"));"

# I would prefer piping
printf 'INSERT INTO active_records (password) VALUES (%s);\n' $(quoteSQL "$PASSWORD") | doSQL

5
这似乎是一个典型的错误使用工具的案例。
你需要大量的工作来实现bash中mysql_real_escape_string()所做的转义。请注意,mysql_real_escape_string()实际上将转义委托给MySQL库,该库考虑了连接和数据库字符集。它被称为“真实”,因为它的前身mysql_escape_string()没有考虑字符集,并且可能被欺骗注入SQL。
我建议使用带有MySQL库的脚本语言,如Ruby、Python或PHP。
如果你坚持使用bash,则使用MySQL Prepared Statements语法。

我不会 Ruby 或 Python,我将 PHP 作为 Apache lib 运行,因此没有 CLI。我想我可以使用 PERL,但我讨厌混合 PERL 和 BASH。脚本所做的其他所有事情都是在 BASH 中完成的。 - ParoX
3
准备语句真的会有所不同吗?在准备语句之前,您仍然需要将值传递给MySQL。 - x-yuri
1
链接已失效,请使用 https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html 代替。 - Murtaza Haji

2
当然,mysql_real_escape_string()只转义单个字符串文字以进行引用,而不是整个语句。您需要清楚该字符串在语句中的用途。根据MySQL手册中关于字符串字面量的部分,插入到字符串字段中时,您只需要转义单引号、双引号、反斜杠和NUL。但是,bash字符串不能包含NUL,因此以下内容应该足够:
#escape for MySQL single string
PASSWORD=${PASSWORD//\\/\\\\}
PASSWORD=${PASSWORD//\'/\\\'}
PASSWORD=${PASSWORD//\"/\\\"}

如果您将在LIKE之后使用该字符串,则还可能需要转义%_。另一个可能性是使用准备好的语句。确保不要在bash中使用echo -e。请参见https://www.owasp.org/index.php/SQL_Injection_Prevention_Cheat_Sheet

0

这是我做的方式,其中my-file.txt包含空格、换行和引号:

IFS='' content=$(cat my-file.txt)
mysql <flags> -e "update table set column = $(echo ${content@Q} | cut -c 2-) where something = 123"

这个无法处理输入 echo "test" > my-file.txt,因为它的输出中有一个额外的 '。使用 cut -c 2- 可以切掉第一个单引号,但是最后一个单引号仍然存在。只使用 echo 来调试替换或者一些带有 mysql -eSELECT $(...); 显示出一些可疑的行为。特别是单引号会以奇怪的方式转义,而在我的情况下双引号没有被转义。 - E. Körner

0

这将转义撇号

a=$(echo "$1" | sed s/"'"/"\\\'"/g)

请注意,mysql_real_escape_string 还会转义 \x00、\n、\r、\、" 和 \x1a。请务必转义它们以确保完全安全。
例如,要转义 \x00:
a=$(echo "$1" | sed s/"\x00"/"\\\'"/g)

稍加努力,您可能可以使用一个sed命令轻松解决这些问题。


0

这里是我写的一些 Bash 函数,分组成一个库。

它提供了适当引用/转义字符串和标识符的方法:

##### db library functions #####

# Executes SQL Queries on localhost's MySQL server
#
# @Env
# $adminDBUser: The database user
# $adminDBPassword: The database user's password
#
# @Params
# $@: Optional MySQL arguments
#
# @Output
# >&1: The MySQL output stream
db::execute() {
  # Uncomment below to debug
  #tee --append debug.sql |
    mysql \
      --batch \
      --silent \
      --user="${adminDBUser:?}" \
      --password="${adminDBPassword:?}" \
      --host=localhost \
      "$@"
}

# Produces a quoted string suitable for inclusion in SQL statements.
#
# @Params
# $1: The string to bo quoted
#
# @Output
# >&1: The quoted identifier suitable for inclusion in SQL statements
db::quoteString() {
  local -- string="${1:?}"
  local -- bas64String && bas64String=$(printf %s "${string}" | base64)
  db::execute <<< "SELECT QUOTE(FROM_BASE64('${bas64String}'));"
}

# Produces a quoted identifier suitable for inclusion in SQL statements.
#
# @Params
# $1: The identifier to bo quoted
#
# @Output
# >&1: The quoted identifier suitable for inclusion in SQL statements
db::quoteIdentifier() {
  local -- identifier="${1:?}"
  local -- bas64Identifier && bas64Identifier=$(printf %s "${identifier}" | base64)
  db::execute <<< "SELECT sys.quote_identifier(FROM_BASE64('${bas64Identifier}'))"
}

0

当然,为什么不使用真正的东西呢?

一个脚本,可以放在任何地方,例如
~/scripts/mysqli_real_escape.php

#!/bin/php
<?php

$std_input_data = '';
$mysqli             = new mysqli('localhost', 'username', 'pass', 'database_name');

if( ftell(STDIN) !== false  )       $std_input_data = stream_get_contents(STDIN);
if( empty($std_input_data)  )       exit('No input piped in');
if( mysqli_connect_errno( ) )       exit('Could not connect to database');

fwrite  (   STDOUT, 
            $mysqli->real_escape_string($std_input_data) 
        );

exit(0);

?>

接下来,在 bash 终端中运行:
chmod +x ~/script/mysqli_real_escape.php`
ln -s ~/script/mysqli_real_escape.php /usr/bin/mysqli_real_escape

准备就绪!现在你可以在你的bash脚本中使用mysqli_real_escape了!
#!/bin/bash
MyString="stringW@#)*special characters"
MyString="$(printf "$MyString" | mysqli_real_escape )"

注意:据我所知,使用"$(cmd ..."$var")"进行命令替换比使用反引号更为推荐。但是,既然不需要进一步嵌套,两种方式都可以。
另外注意:在命令替换的内部,"$(...)"会创建一个新的引号上下文。这就是为什么变量周围的引号不会破坏字符串的原因。

-2

这个会起作用:

echo "John O'hara"  | php -R 'echo addslashes($argn);'

传递给一个变量:

name=$(echo "John O'hara"  | php -R 'echo addslashes($argn);')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接