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
以及使用上述命令的示例:
示例.sh:
PASSWORD=$1
doSQL "INSERT INTO active_records (password) VALUES ('$PASSWORD')"
如果密码中包含单引号,显然这会失败。
在 Bash 中,
printf
可以为你进行转义:
$ a=''\''"\;:#[]{}()|&^$@!?, .<>abc123'
$ printf -v var "%q" "$a"
$ echo "$var"
\'\"\\\;:#\[\]\{\}\(\)\|\&\^\$@\!\?\,\ .\<\>abc123
我会让你来决定这是否足够激进。
无论您使用什么引号,都无法逃脱以下构造:
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
这似乎是使用错误工具完成工作的典型案例。
您还有大量工作要做,要在 bash 中实现由 mysql_real_escape_string()
完成的转义。请注意,
mysql_real_escape_string()
实际上将转义委托给MySQL库,该库考虑了连接和数据库字符集。它之所以被称为“真实”,是因为它的前身
mysql_escape_string()
没有考虑字符集,并且可能被欺骗注入SQL。我建议使用具有 MySQL 库的脚本语言,例如 Ruby、Python 或 PHP。
如果您坚持使用 bash,请使用
MySQL 准备语句 语法。
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这将避开撇号
a=$(echo "$1" | sed s/"'"/"\\\'"/g)
请注意 mysql_real_escape_string 也会转义 \x00, , 、 \、 " 和 \x1a。为了完全安全,请务必转义这些内容。
例如要转义 \x00:
a=$(echo "$1" | sed s/"\x00"/"\\\'"/g)
只要稍加努力,您就可以使用一个 sed 命令来逃避这些。
当然,为什么不直接使用真实的东西呢?
〜/脚本/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")"
的命令替换优于使用反引号。然而,由于不需要进一步的嵌套,所以两者都应该没问题。
进一步注意:当在命令替换中时,
"$(...)"
,会创建一个新的引用上下文。这就是变量周围的引号不会弄乱字符串的原因。 我就是这样做的,其中
IFS='' content=$(cat my-file.txt)
mysql <flags> -e "update table set column = $(echo ${content@Q} | cut -c 2-) where something = 123"
这是我编写的几个 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}'))"
}
简短回答:使用
如果您的值具有美元字符,例如:
$p123$hello
,那么在 bash 中您可以通过 \
来转义。那么字符串将是
\$p123\$hello
以我为例,我已经这样更改密码:
mysql> update users set password='\$p123\$hello' where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
这会起作用:
echo "John O'hara" | php -R 'echo addslashes($argn);'
将其传递给变量:
name=$(echo "John O'hara" | php -R 'echo addslashes($argn);')