وسيطات الهروب لـ paramiko.sshclient (). exec_command
سؤال
ما هي أفضل طريقة للهروب من سلسلة للاستخدام الآمن كوسيطة سطر القيادة؟ أعلم ذلك باستخدام subprocess.Popen
يعتني بهذا باستخدام list2cmdline()
, ، لكن هذا لا يبدو أنه يعمل بشكل صحيح مع Paramiko. مثال:
from subprocess import Popen
Popen(['touch', 'foo;uptime']).wait()
هذا ينشئ ملف اسمه حرفيًا foo;uptime
, وهذا ما أريد. قارن:
from paramiko import SSHClient()
from subprocess import list2cmdline
ssh = SSHClient()
#... load host keys and connect to a server
stdin, stdout, stderr = ssh.exec_command(list2cmdline(['touch', 'foo;uptime']))
print stdout.read()
هذا يخلق ملفا يسمى foo
ويطبع وقت تشغيل المضيف عن بُعد. لقد نفذت uptime
كأمر ثانٍ بدلاً من استخدامه كجزء من الوسيطة إلى الأمر الأول ، touch
. هذا ليس ما أريد.
حاولت الهروب من فاصلة فاصلة مع تراجع خلفي قبل وبعد إرسالها إلى list2cmdline
, ، ولكن بعد ذلك انتهيت مع ملف يسمى foo\;uptime
.
أيضًا ، يعمل بشكل صحيح إذا بدلاً من uptime
, ، يمكنك استخدام أمر مع مساحة:
stdin, stdout, stderr = ssh.exec_command(list2cmdline(['touch', 'foo;echo test']))
print stdout.read()
هذا يخلق ملفًا يسمى حرفيًا foo;echo test
لان list2cmdline
أحاطت مع اقتباسات.
أيضا ، حاولت pipes.quote()
وكان له نفس التأثير مثل list2cmdline
.
تحرير: للتوضيح ، أحتاج إلى التأكد من تنفيذ أمر واحد فقط على المضيف البعيد ، بغض النظر عن بيانات الإدخال التي أتلقاها ، مما يعني الهروب من الأحرف مثل ;
, &
, و backtick.
المحلول
على افتراض أن المستخدم البعيد لديه قذيفة posix ، يجب أن ينجح هذا:
def shell_escape(arg):
return "'%s'" % (arg.replace(r"'", r"'\''"), )
لماذا هذا العمل؟
ونقلت Posix Shell Single يتم تعريفها على النحو التالي:
يجب أن تحتفظ بأحرف الأحرف في الربع المفرد ('') القيمة الحرفية لكل حرف داخل الربعات الواحدة. لا يمكن أن تحدث كمية واحدة داخل الربعات الواحدة.
الفكرة هنا هي أنك ترفق السلسلة في اقتباسات واحدة. هذا ، وحده ، جيد بما فيه الكفاية --- كل شخصية باستثناء اقتباس واحد سيتم تفسيره حرفيًا. للحصول على عروض أسعار واحدة ، يمكنك الخروج من السلسلة المفردة (الأولى '
) ، أضف عرض أسعار واحد ( \'
) ، ثم استئناف السلسلة المفردة (الأخيرة '
).
ماذا يعمل هذا؟
هذا يجب أن يعمل مع أي قذيفة posix. لقد اختبرته مع داش وباش. سولاريس 5.10 /bin/sh
(الذي أعتقد أنه غير متوافق مع Posix ، ولم أتمكن من العثور على مواصفات) يبدو أنه يعمل أيضًا.
للمضيفين البعيد التعسفي ، أعتقد أن هذا مستحيل. أظن ssh
سيتم تنفيذ الأمر الخاص بك مع أي قذيفة المستخدم عن بُعد (كما تم تكوينها في /etc/passwd
أو ما يعادلها). إذا كان المستخدم البعيد يعمل ، على سبيل المثال ، على سبيل المثال ، /usr/bin/python
أو git-shell
أو شيء من هذا القبيل ، ليس فقط أي مخطط اقتباس من المحتمل أن يصادف التناقضات عبر القشرة ، ولكن من المحتمل أن يفشل تنفيذ الأمر أيضًا.
CSH / TCSH
أكثر مشكلة بقليل هو احتمال تشغيل المستخدم البعيد tcsh
, ، نظرًا لأن بعض الأشخاص يديرون ذلك بالفعل في البرية وقد يتوقعون باراميكو exec_command
للعمل. (مستخدمي /usr/bin/python
كقذيفة ربما ليس لديها مثل هذه التوقعات ...)
يبدو أن TCSH يعمل في الغالب. ومع ذلك ، لا يمكنني اكتشاف طريقة لاقتباس سطر جديد بحيث يكون سعيدًا. يبدو أن خطًا جديدًا في سلسلة واحدة مقبلة يجعل TCSH غير سعيد:
$ tcsh -c $'echo \'foo\nbar\''
Unmatched '.
Unmatched '.
بخلاف الخطوط الجديدة ، يبدو أن كل شيء حاولت يعمل مع TCSH (بما في ذلك عروض الأسعار الفردية ، والاقتباسات المزدوجة ، وتراجع الخلايا ، وعلامات التبويب المضمنة ، العلامات النجمية ، ...).
اختبار قذيفة الهروب
إذا كان لديك مخطط هروب ، فإليك بعض الأشياء التي قد ترغب في اختبارها مع:
- تسلسلات الهروب (
\n
,\t
, ...) - يقتبس (
'
,"
,\
) - شخصيات غرباء (
*
,?
,[]
, ، إلخ.) - مراقبة الوظائف وخطوط الأنابيب (
|
,&
,||
,&&
, ...) - الخطوط الجديدة
الخطوط الجديدة تستحق ملاحظة خاصة. ال re.escape
المحلول لا يتعامل مع هذا بشكل صحيح --- يهرب من أي حرف غير رقمي ، و Posix Shell يعتبر خطًا جديدًا هربًا (أي في بيثون ، السلسلة ذات الأحرف "\\\n"
) لتكون صفوفًا صفرًا ، وليس حرفًا جديدًا. أنا فكر في re.escape
يتعامل مع جميع الحالات الأخرى بشكل صحيح ، على الرغم من أنه يخيفني لاستخدام شيء مصمم للتعبيرات العادية للقيام بالهروب من أجل الصدفة. قد يتحول الأمر إلى العمل ، لكنني أشعر بالقلق من وجود حالة خفية re.escape
أو قواعد الهروب من الصدفة (مثل الخطوط الجديدة) ، أو التغييرات المستقبلية المحتملة في واجهة برمجة التطبيقات.
يجب أن تدرك أيضًا أن تسلسل الهروب يمكن أن تتم معالجته في مراحل مختلفة ، مما يعقد اختبار الأشياء --- أنت تهتم فقط بما تنقله القشرة إلى البرنامج ، وليس ما يفعله البرنامج. استخدام printf "%s\n" escaped-string-to-test
ربما يكون أفضل رهان. echo
يعمل بشكل مدهش بشكل مدهش: في داش ، echo
العمليات المدمجة يهرب الخلاف مثل \n
. استخدام /bin/echo
عادة ما يكون آمنًا ، ولكن على جهاز Solaris 5.10 الذي اختبرته ، فإنه يتعامل أيضًا مع تسلسل مثل \n
.
نصائح أخرى
أنت لا تنجح مع list2cmdline()
نظرًا لأنه يستهدف سطر أوامر Microsoft ، والذي يحتوي على قواعد مختلفة عن سطر أوامر POSIX الذي تتواصل معه باستخدام SSH.
بدلاً من ذلك ، استخدم روتين بيثون الأصلي pipes.quote()
, ، وكن حذرًا لتطبيقه بشكل منفصل على كل وسيطة في الأمر. سيعطيك هذا سطر أوامر عمل لـ SSH:
from pipes import quote
command = ['touch', 'foo;uptime']
print ' '.join(quote(s) for s in command)
يقتبس الإخراج بعناية الوسيطة الثانية لحماية ;
حرف:
touch 'foo;uptime'
re.escape()
هو ما أبحث عنه.
يكرر.*هرب(**سلسلة*)
إرجاع ** سلسلة*مع كل غير الألفانومريكات المتراجع ...
مثال:
from paramiko import SSHClient()
from subprocess import list2cmdline
import re
ssh = SSHClient()
#... load host keys and connect to a server
stdin, stdout, stderr = ssh.exec_command(' '.join(['touch', re.escape('foo;uptime')]))
هذا ينشئ ملفًا على الخادم يسمى foo;uptime
, وهذا ما أريد.
لقد جربت كل شحنات meta shell التي يمكنني التفكير فيها وهي تعمل:
stdin, stdout, stderr = ssh.exec_command(' '.join(['touch', re.escape('test;rm foo&echo "Uptime: `uptime`"')]))