diff --git a/README.md b/README.md index 2cfb21c8..f6b8aa7e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,69 @@ -# HolyUB - A website that can be used to bypass web filters; both online and firewall. +# Alloy Proxy + +A node.js proxy that features URL encoding, and amazing compatablity! + +[](https://heroku.com/deploy?template=https://github.com/titaniumnetwork-dev/alloyproxy/) + +# How to install and use: + +`git clone https://github.com/titaniumnetwork-dev/alloyproxy.git` + +`cd alloyproxy` + +`npm install` + +`npm start` + +The default place for the proxy when its started is `http://localhost:8080` but feel free to change it in config.json! + +# How the proxy works: + +The proxy works by using node-fetch (Basically Window.fetch ported to Node-js). +Basically what the app is doing is node-fetch is sending the request to the server then +the app sends the response back to the server with the modifactions made to the attributes and elements. + +When a attribute is rewritten, depending on the contents inside. It will turn: + +`href="/assets/js/main.js"` into `href="/fetch/websiteURL/assets/js/main.js"`. + +A porition of its rewriting is in client-side JS so `Element.setAttribute`, `window.fetch()`, XMLHttpRequest, and more are rewritten. + +# Extra information: + +If your gonna have an external website redirect to this proxy. Then we recommend you have the value base64 encoded and redirected to `/alloy?url=` then value. + +# Deploying to Heroku: + +If your gonna be hosting this on something like Heroku. You need to make sure SSL mode is turned off so this will work. + +# Known websites that work + +- Google Search + +- Discord + +- LittleBigSnake + +- Surviv.io + +- Youtube + +- Y8 + +- 1v1.LOL + +- Old Reddit + +and plenty more! + +# Known issues that need to be fixed + +- Better POST body parsing instead of using body-parser. + +- Cookie header rewriting + +# Updates to come in the future + +- Full URL encoding / encryption mode + +- Websocket proxing diff --git a/alloy/assets/error.html b/alloy/assets/error.html new file mode 100644 index 00000000..3577583a --- /dev/null +++ b/alloy/assets/error.html @@ -0,0 +1,88 @@ + +
+ + + + + + +AlloyProxy
+%ERROR%
+ + \ No newline at end of file diff --git a/alloy/assets/inject.js b/alloy/assets/inject.js new file mode 100644 index 00000000..76c28bac --- /dev/null +++ b/alloy/assets/inject.js @@ -0,0 +1,209 @@ +// Ajax Rewriting + +let apData = document.getElementById('alloyData'); +let urlData = apData.getAttribute('data-alloyURL'); + + +function rewriteURL(url, encoding) { + var websiteURL + if (encoding == 'base64') { + websiteURL = btoa(url.split('/').splice(0, 3).join('/')) + } else { + websiteURL = url.split('/').splice(0, 3).join('/') + } + const path = '/' + url.split('/').splice(3).join('/') + var rewritten + if (path == '/') { + rewritten = '/fetch/' + websiteURL + } else { + rewritten = `/fetch/${websiteURL}${path}` + } + return rewritten +} +let ajaxRewrite = window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) { + if (url.startsWith(`${window.location.protocol}//${window.location.hostname}`) && !url.startsWith(`${window.location.protocol}//${window.location.hostname}/fetch/`)) { + url = `/fetch/${urlData}/` + url.split('/').splice(3).join('/') + } else if (url.startsWith('http')) { + const hostname = url.split('/').slice(0, 3).join('/') + const path = url.split('/').slice(3).join('/') + const encodedHost = btoa(hostname) + const fullURL = encodedHost + '/' + path + url = '/fetch/' + fullURL + } else if (url.startsWith('//')) { + const encodedURL = btoa('http:' + url) + url = '/alloy/?url=' + encodedURL + } else if (url.startsWith('/')) { + if (url.startsWith('/fetch')) { + url = url + } else if (url.startsWith('/alloy')) { + url = url + } else { + let apData = document.getElementById('alloyData'); + let urlData = apData.getAttribute('data-alloyURL'); + url = '/fetch/' + urlData + url + } + } + + + return ajaxRewrite.apply(this, arguments); + } + + let windowFetchRewrite = window.fetch;window.fetch = function(url) { + if (url.startsWith(`https://${window.location.hostname}`)) { + url = url + } else if (url.startsWith('http')) { + const hostname = url.split('/').slice(0, 3).join('/') + const path = url.split('/').slice(3).join('/') + const encodedHost = btoa(hostname) + const fullURL = encodedHost + '/' + path + url = '/fetch/' + fullURL + } else if (url.startsWith('//')) { + const encodedURL = btoa('http:' + url) + url = '/alloy/?url=' + encodedURL + } else if (url.startsWith('/')) { + if (url.startsWith('/fetch')) { + url = url + } else if (url.startsWith('/alloy')) { + url = url + } else { + let apData = document.getElementById('alloyData'); + let urlData = apData.getAttribute('data-alloyURL'); + url = '/fetch/' + urlData + url + } + } + return windowFetchRewrite.apply(this, arguments); + } + + + + //Create Element rewriting + var original = document.createElement; + document.createElement = function (tag) { + var element = original.call(document, tag); + if (tag.toLowerCase() === 'script') { + Object.defineProperty(element.__proto__, 'src', { + set: function(newValue) { + if (newValue.startsWith('/fetch/')) { + element.setAttribute('src', newValue) + } else if (newValue.startsWith('/alloy/')) { + element.setAttribute('src', newValue) + } else if (newValue.startsWith(`https://${window.location.hostname}`)) { + element.setAttribute('src', newValue) + } else if (newValue.startsWith('//')) { + const encodedURL = btoa('http:' + newValue) + element.setAttribute('src', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('https://')) { + const encodedURL = btoa(newValue) + element.setAttribute('src', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('http://')) { + const encodedURL = btoa(newValue) + element.setAttribute('src', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('/')) { + element.setAttribute('src', '/fetch/' + urlData + newValue) + } else { + element.setAttribute('src', newValue) + } + } + }); + } else if (tag.toLowerCase() === 'iframe') { + Object.defineProperty(element.__proto__, 'src', { + set: function(newValue) { + if (newValue.startsWith('/fetch/')) { + element.setAttribute('src', newValue) + } else if (newValue.startsWith('/alloy/')) { + element.setAttribute('src', newValue) + } else if (newValue.startsWith(`https://${window.location.hostname}`)) { + element.setAttribute('src', newValue) + } else if (newValue.startsWith('//')) { + const encodedURL = btoa('http:' + newValue) + element.setAttribute('src', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('https://')) { + const encodedURL = btoa(newValue) + element.setAttribute('src', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('http://')) { + const encodedURL = btoa(newValue) + element.setAttribute('src', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('/')) { + element.setAttribute('src', '/fetch/' + urlData + newValue) + + } else { + element.setAttribute('src', newValue) + } + } + }); + } + else if (tag.toLowerCase() === 'link') { + Object.defineProperty(element.__proto__, 'href', { + set: function(newValue) { + if (newValue.startsWith('/fetch/')) { + element.setAttribute('href', newValue) + } else if (newValue.startsWith('/alloy/')) { + element.setAttribute('href', newValue) + } else if (newValue.startsWith(`https://${window.location.hostname}`)) { + element.setAttribute('href', newValue) + } else if (newValue.startsWith('//')) { + const encodedURL = btoa('http:' + newValue) + element.setAttribute('href', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('https://')) { + const encodedURL = btoa(newValue) + element.setAttribute('href', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('http://')) { + const encodedURL = btoa(newValue) + element.setAttribute('href', '/alloy/?url=' + encodedURL) + } else if (newValue.startsWith('/')) { + element.setAttribute('href', '/fetch/' + urlData + newValue) + + } else { + element.setAttribute('href', newValue) + } + } + }); + } + return element; + } + + + let setAttributeRewrite = window.Element.prototype.setAttribute;window.Element.prototype.setAttribute = function(name, value) { + switch(name) { + case 'src': + if (value.startsWith('/fetch/')) { + value = value + } else if (value.startsWith('/alloy/')) { + value = value + } else if (value.startsWith('//')) { + value = rewriteURL('http:' + value, 'base64') + } else if (value.startsWith('/')) { + value = rewriteURL(urlData + value) + break; + } else if (value.startsWith('https://') || value.startsWith('http://')) { + value = rewriteURL(value, 'base64') + } else { + value = value + } + break; + case 'href': + if (value.startsWith('/fetch/')) { + value = value + } else if (value.startsWith('//')) { + value = rewriteURL('http:' + value, 'base64') + } else if (value.startsWith('/')) { + value = rewriteURL(urlData + value) + break; + } else if (value.startsWith('https://') || value.startsWith('http://')) { + value = rewriteURL(value, 'base64') + } else { + value = value + } + break; + } + + + return setAttributeRewrite.apply(this, arguments); + } + + var previousState = window.history.state; + setInterval(function() { + if (!window.location.pathname.startsWith(`/fetch/${urlData}/`)) { + history.replaceState('', '', `/fetch/${urlData}/${window.location.href.split('/').splice(3).join('/')}`); + } + }, 0.1); \ No newline at end of file diff --git a/app.js b/app.js index 3814769e..08442b89 100644 --- a/app.js +++ b/app.js @@ -1,79 +1,334 @@ -/*************** - * node-unblocker: Web Proxy for evading firewalls and content filters, - * similar to CGIProxy or PHProxy - * - * - * This project is hosted on github: https://github.com/nfriedly/nodeunblocker.com - * - * By Nathan Friedly - http://nfriedly.com - * Released under the terms of the Affero GPL v3 - */ - -var url = require('url'); -var querystring = require('querystring'); +var https = require('https'); +var http = require('http'); +var fetch = require('node-fetch'); var express = require('express'); -var unblocker = require('unblocker'); -var Transform = require('stream').Transform; - +var fs = require('fs'); var app = express(); +var cookieParser = require('cookie-parser'); +var session = require('express-session'); -var google_analytics_id = process.env.GA_ID || null; +var config = JSON.parse(fs.readFileSync('config.json', 'utf-8')), + httpsAgent = new https.Agent({ + rejectUnauthorized: false, + keepAlive: true, + }), + httpAgent = new http.Agent({ + rejectUnauthorized: false, + keepAlive: true, + }), + ssl = { key: fs.readFileSync('ssl/default.key', 'utf8'), cert: fs.readFileSync('ssl/default.crt', 'utf8') }, + server, + port = process.env.PORT || config.port, + ready = (() => { + var a = 'http://', b = config.listenip; + if (config.ssl) a = 'https://'; + if (b == '0.0.0.0' || b == '127.0.0.1') b = 'localhost'; + console.log('AlloyProxy is now running at', a + b + ':' + port); + }); -function addGa(html) { - if (google_analytics_id) { - var ga = [ - "" - ].join("\n"); - html = html.replace("