سؤال

ما هي أفضل طريقة للهروب من سلسلة للاستخدام الآمن كوسيطة سطر القيادة؟ أعلم ذلك باستخدام 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`"')]))
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top