Bug 1116485 (CVE-2014-3539)

Summary: CVE-2014-3539 python-rope: pickle.load of remotely supplied data with no authentication required
Product: [Other] Security Response Reporter: Kurt Seifried <kseifried>
Component: vulnerabilityAssignee: Red Hat Product Security <security-response-team>
Status: CLOSED DEFERRED QA Contact:
Severity: medium Docs Contact:
Priority: medium    
Version: unspecifiedCC: jrusnack, mcepl, nicks, security-response-team
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2015-07-07 19:38:35 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On: 1240804    
Bug Blocks:    
Attachments:
Description Flags
suggested patch none

Description Kurt Seifried 2014-07-05 02:22:42 UTC
Kurt Seifried and Vasyl Kaigorodov of Red Hat Product Security report:

So while I was looking through the source I spotted a flaw, Vasyl confirmed it

============================================================
python rope
http://rope.sourceforge.net/
https://pypi.python.org/pypi/rope
229 downloads in the last day
1455 downloads in the last week
5361 downloads in the last month
============================================================

pickle.load of remotely supplied data with no auth, RCE

============================================================
rope/base/oi/doa.py
==============================
class _SocketReceiver(_MessageReceiver):
def receive_data(self):
conn, addr = self.server_socket.accept()
self.server_socket.close()
my_file = conn.makefile('r')
while True:
try:
yield pickle.load(my_file)
except EOFError:
break
my_file.close()
conn.close()


class PythonFileRunner(object):
def _init_data_receiving(self):
if self.analyze_data is None:
return
Show quoted text
if True or os.name == 'nt':
self.receiver = _SocketReceiver()

============================================================
/rope/base/pycore.py
==============================
class PyCore(object):
def run_module(self, resource, args=None, stdin=None, stdout=None):
"""Run `resource` module

Returns a `rope.base.oi.doa.PythonFileRunner` object for
controlling the process.

"""
perform_doa = self.project.prefs.get('perform_doi', True)
perform_doa = self.project.prefs.get('perform_doa', perform_doa)
receiver = self.object_info.doa_data_received
if not perform_doa:
receiver = None
runner = rope.base.oi.doa.PythonFileRunner(
self, resource, args, stdin, stdout, receiver)
runner.add_finishing_observer(self.module_cache.forget_all_data)
runner.run()
return runner
============================================================

Vasyl Kaigorodov (coworker) then confirmed it was network reachable:

Confirmed:

...
import rope.base.pycore
import rope.base.project

myproject = rope.base.project.Project('.')
res = myproject.get_resource("mod2.py")
myproject.pycore.run_module(res)
...

When run_module() executed - rope really opens a port:
...
tcp        0      0 0.0.0.0:3037            0.0.0.0:*
LISTEN      31234/python
...
and I was able to easily send some "pickled" python code to it (using
nc), which then was executed.
The window of opportunity is really small though (around 0.8 second
for a 2kb file):
...
17:00:53.432052 IP localhost.59128 > localhost.hp-san-mgmt: Flags [S],
seq 3302699683, win 43690, options [mss 65495,sackOK,TS val 533783316
ecr 0,nop,wscale 7], length 0
17:00:53.432075 IP localhost.hp-san-mgmt > localhost.59128: Flags
[S.], seq 3571033229, ack 3302699684, win 43690, options [mss
65495,sackOK,TS val 533783316 ecr 533783316,nop,wscale 7], length 0
17:00:53.432088 IP localhost.59128 > localhost.hp-san-mgmt: Flags [.],
ack 1, win 342, options [nop,nop,TS val 533783316 ecr 533783316], length 0
17:00:54.198028 IP localhost.59128 > localhost.hp-san-mgmt: Flags
[P.], seq 1:2230, ack 1, win 342, options [nop,nop,TS val 533784082
ecr 533783316], length 2229
17:00:54.198055 IP localhost.59128 > localhost.hp-san-mgmt: Flags
[F.], seq 2230, ack 1, win 342, options [nop,nop,TS val 533784082 ecr
533783316], length 0
17:00:54.198055 IP localhost.hp-san-mgmt > localhost.59128: Flags [.],
ack 2230, win 1012, options [nop,nop,TS val 533784082 ecr 533784082],
length 0
17:00:54.211353 IP localhost.hp-san-mgmt > localhost.59128: Flags
[F.], seq 1, ack 2231, win 1024, options [nop,nop,TS val 533784096 ecr
533784082], length 0
17:00:54.211378 IP localhost.59128 > localhost.hp-san-mgmt: Flags [.],
ack 2, win 342, options [nop,nop,TS val 533784096 ecr 533784096], length 0
...
Also according to the rope code (base/oi/doa.py, lines 113-117) , if
port 3037 is taken already - rope will try to bind to 3038, 3039 and
so on - so there's even no 100% guarantee that the port will be static.

Comment 1 Matěj Cepl 2015-02-11 21:38:23 UTC
At least some fix for this was suggested in https://github.com/python-rope/rope/pull/107 upstream.

It doesn't resolve the issue, but at least users who want to use DOA should be intentional about it and they should be warned against possible security risks.

Comment 2 Matěj Cepl 2015-02-11 21:39:21 UTC
Created attachment 990653 [details]
suggested patch

Comment 3 Kurt Seifried 2015-07-07 19:38:04 UTC
Created python-rope tracking bugs for this issue:

Affects: fedora-all [bug 1240804]

Comment 4 nicks 2019-06-28 10:47:46 UTC
This issue was fixed as of rope 0.11.0.

https://github.com/python-rope/rope/pull/251